Использование внутренних классов в C#

каковы наилучшие методы использования и структуры внутренних классов в C#.

например, если у меня очень большой базовый класс и два больших внутренних класса, я должен разделить их на отдельные (частичный класс) кодефайлы или оставить их как один очень большой громоздкий кодефайл?

также это плохая практика иметь абстрактный класс с публичным унаследованным внутренним классом?

9 ответов


обычно я резервирую внутренние классы для одной из двух целей:

  1. открытые классы, производные от родительского класса, где родительский класс является абстрактной базовой реализацией с одним или несколькими абстрактными методами, и каждый подкласс является реализацией, которая служит определенной реализации. после прочтения дизайна фреймворка и рекомендаций я вижу, что это отмечено как "избежать", однако я использую его в сценариях, подобных перечислениям-althogh, что, вероятно, дает плохое впечатление также

  2. внутренние классы являются частными и являются единицами бизнес-логики или иным образом тесно связаны с их родительским классом таким образом, что они принципиально нарушаются при потреблении или использовании любым другим классом.

для всех других случаев я пытаюсь сохранить их в том же пространстве имен и том же уровне доступности, что и их потребитель / логический родитель-часто с именами, которые немного менее дружелюбны, чем " main" класс.

в больших проектах вы были бы удивлены, как часто вы можете обнаружить, что изначально строите сильно связанный компонент только потому, что это первая или основная цель делает его логичным, однако, если у вас нет очень хорошей или технической причины заблокировать его и скрыть от глаз, тогда нет никакого вреда в разоблачении класса, чтобы другие компоненты могли его потреблять.

редактировать имейте в виду, что, хотя мы говорим о подклассах, они должны быть более или менее хорошо спроектированные и слабо связанные компоненты. Даже если они являются частными и невидимыми для внешнего мира, сохранение минимальной "площади поверхности" между классами значительно облегчит ремонтопригодность вашего кода для будущего расширения или изменения.


у меня нет книги, но руководство по дизайну рамок предлагает использовать public внутренние классы, пока клиенты не должны ссылаться на имя класса. private внутренние классы в порядке: никто не заметит их.

плохое: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

хорошо: listView.Items.Add(...);

что касается вашего большого класса: обычно стоит разделить что-то вроде этого на меньшие классы, каждый с одной конкретной частью функциональность. Поначалу его трудно разорвать, но я предсказываю, что позже это облегчит вашу жизнь...


обычно внутренние классы должны быть частными и использоваться только классом, который их содержит. Если их внутренние классы очень велики, это предполагает, что они должны быть их собственными классами.

обычно, когда у вас есть большой внутренний класс, это потому, что внутренний класс тесно связан с ним, содержащим класс и нуждается в доступе к его частным методам.


Я думаю, что это довольно субъективно, но я бы, вероятно, разделил их на отдельные файлы кода, сделав класс "хост" частичным.

делая так, вы можете получить еще больше обзора по редактирование файла проекта чтобы сделать группу файлов так же, как дизайнерские классы в Windows Forms. Я думаю, что видел добавление Visual Studio, которое делает это автоматически для вас, но я не помню, где.

EDIT:
После некоторых поисков я нашел надстройку Visual Studio для этого под названием VSCommands


о только как структура такого зверя ...

вы можете использовать частичные классы для разделения основного класса и вложенных классов. Когда вы это сделаете, вам рекомендуется назвать файлы соответствующим образом, чтобы было очевидно, что происходит.

// main class in file Outer.cs
namespace Demo
{
  public partial class Outer
  {
     // Outer class
  }
}

// nested class in file Outer.Nested1.cs
namespace Demo
{
  public partial class Outer
  {
    private class Nested1
    {
      // Nested1 details
    }
  }
}

точно так же вы часто видите (явные) интерфейсы в своем собственном файле. например,Outer.ISomeInterface.cs вместо редактора по умолчанию #regioning их.

ваша структура файлов проектов затем начинает выглядеть как

   /Project/Demo/ISomeInterface.cs
   /Project/Demo/Outer.cs
   /Project/Demo/Outer.Nested1.cs
   /Project/Demo/Outer.ISomeInterface.cs

обычно, когда мы делаем это для изменения шаблона.


Мне лично нравится иметь один класс для каждого файла и внутренние классы как часть этого файла. Я считаю, что внутренние классы обычно (почти всегда) должны быть частными и являются деталью реализации класса. Наличие их в отдельном файле путает вещи, IMO.

использование областей кода для обертывания внутренних классов и скрытия их деталей хорошо работает для меня в этом случае и не позволяет работать с файлом. Области кода сохраняют внутренний класс " скрытым", и поскольку это частная деталь реализации, это нормально для меня.


Я лично использую внутренние классы для инкапсуляции некоторых концепций и операций, используемых только внутри класса. Таким образом, я не загрязняю непубличный api этого класса и сохраняю api чистым и компактным.

вы можете воспользоваться частичными классами, чтобы переместить определение этих внутренних классов в другой файл для лучшей orgnanization. VS автоматически не группирует файлы частичных классов для вас, за исключением некоторых шаблонных элементов, таких как ASP.NET, WinForm форм и т. д. Вам нужно будет отредактировать файл проекта и внести в него некоторые изменения. Вы можете посмотреть на одну из существующих группировок, чтобы увидеть, как это делается. Я считаю, что есть некоторые макросы, которые позволяют частичную Class файлы в обозревателе решений.


на мой взгляд, внутренние классы, если это вообще необходимо, должны быть небольшими и использоваться только этим классом. Если вы используете Relfector в .NET framework, вы увидите, что они часто используются именно для этой цели.

Если ваши внутренние классы становятся слишком большими, я бы определенно переместил их в отдельные классы / кодовые файлы как-то, если только для ремонтопригодности. Я должен поддерживать некоторый существующий код, где кто-то думал, что это отличная идея использовать внутренние классы внутри внутренних классов. Он результатом стала внутренняя иерархия классов глубиной от 4 до 5 уровней. Излишне говорить, что код непроницаем и занимает века, чтобы понять, на что вы смотрите.


здесь см. практический пример вложенного класса, который может дать вам представление об их использовании (добавлен некоторый модульный тест)

namespace CoreLib.Helpers
{
    using System;
    using System.Security.Cryptography;

    public static class Rnd
    {
        private static readonly Random _random = new Random();

        public static Random Generator { get { return _random; } }

        static Rnd()
        {
        }

        public static class Crypto
        {
            private static readonly RandomNumberGenerator _highRandom = RandomNumberGenerator.Create();

            public static RandomNumberGenerator Generator { get { return _highRandom; } }

            static Crypto()
            {
            }

        }

        public static UInt32 Next(this RandomNumberGenerator value)
        {
            var bytes = new byte[4];
            value.GetBytes(bytes);

            return BitConverter.ToUInt32(bytes, 0);
        }
    }
}

[TestMethod]
public void Rnd_OnGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Generator;
    var rdn2 = Rnd.Generator;
    var list = new List<Int32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                lock (list)
                {
                    list.Add(Rnd.Generator.Next(Int32.MinValue, Int32.MaxValue));
                }
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}

[TestMethod]
public void Rnd_OnCryptoGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Crypto.Generator;
    var rdn2 = Rnd.Crypto.Generator;
    var list = new ConcurrentQueue<UInt32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                    list.Enqueue(Rnd.Crypto.Generator.Next());
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}