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



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



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



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

524   9  

9 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Редактировать:
После некоторого поиска я нашел надстройку 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, а не редактор по умолчанию #region ing them.

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

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

Обычно, когда мы это делается для вариации шаблона строителя.

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

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

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

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

По моему мнению, внутренние классы, если они вообще нужны, должны быть небольшими и использоваться только этим классом. Если вы используете 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);
}

Comments

    Ничего не найдено.