Вопрос

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

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

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

Это было полезно?

Решение

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

  1. Открытые классы, производные от родительского класса, где родительский класс представляет собой абстрактную базовую реализацию с одним или несколькими абстрактными методами, а каждый подкласс представляет собой реализацию, обслуживающую конкретную реализацию. после прочтения Framework Design and Guidelines я вижу, что это помечено как «Избегать», однако я использую его в сценариях, похожих на перечисления, хотя это, вероятно, тоже производит плохое впечатление.

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

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

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

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

Другие советы

У меня нет книги под рукой, но в Руководстве по проектированию фреймворка предлагается использовать 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их.

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

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

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

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

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

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

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

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

Если ваши внутренние классы становятся слишком большими, я бы определенно переместил их в отдельные классы/кодовые файлы, хотя бы для удобства обслуживания.Мне нужно поддерживать существующий код, в котором кто-то посчитал отличной идеей использовать внутренние классы внутри внутренних классов.Это привело к тому, что внутренняя иерархия классов достигла глубины 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);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top