Использование внутренних классов в C#
Каковы лучшие практики, касающиеся использования и структуры внутренних классов в C#.
Например, если у меня очень большой базовый класс и два больших внутренних класса, я должен разделить их на отдельные кодовые файлы (частичный класс) или оставить их как один очень большой громоздкий кодовый файл?
Также плохая практика иметь абстрактный класс с открытым унаследованным внутренним классом?
9 ответов
Обычно я резервирую inner-классы для одной из двух целей:
Открытые классы, которые наследуются от своего родительского класса, где родительский класс является абстрактной базовой реализацией с одним или несколькими абстрактными методами, а каждый подкласс является реализацией, которая обслуживает конкретную реализацию. после прочтения Framework Design and Guidelines я вижу, что это помечено как "Избегать", однако я использую его в сценариях, похожих на enums- хотя это, вероятно, также производит плохое впечатление
Внутренние классы являются частными и являются единицами бизнес-логики или иным образом тесно связаны с родительским классом таким образом, что они в корне нарушаются при использовании или использовании любым другим классом.
Во всех остальных случаях я стараюсь держать их в том же пространстве имен и на том же уровне доступности, что и их потребитель / логический родитель - часто с именами, которые немного менее дружелюбны, чем "основной" класс.
В больших проектах вы будете удивлены, как часто вы обнаружите, что изначально создаете сильно связанный компонент, просто потому, что его первая или основная цель делает его логичным - однако, если у вас нет очень веской или технической причины заблокировать его и скрыть это с первого взгляда, тогда нет ничего плохого в том, чтобы выставить класс так, чтобы его могли использовать другие компоненты.
Помните, что, хотя мы говорим о подклассах, они должны быть более или менее хорошо спроектированными и слабо связанными компонентами. Даже если они являются закрытыми и невидимыми для внешнего мира, сохранение минимальной "площади поверхности" между классами значительно облегчит поддержку вашего кода для будущего расширения или изменения.
У меня нет книги в руках, но в Руководстве по разработке инфраструктуры предлагается использовать public
внутренние классы, пока клиенты не должны ссылаться на имя класса. private
с внутренними классами все в порядке: их никто не заметит.
Плохой: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();
Хорошо: listView.Items.Add(...);
Что касается вашего большого класса: как правило, стоит разбить что-то подобное на более мелкие классы, каждый с одним конкретным компонентом функциональности. Поначалу это трудно сломать, но я предсказываю, что это сделает вашу жизнь легче в дальнейшем...
Обычно внутренние классы должны быть закрытыми и использоваться только тем классом, который их содержит. Если их внутренние классы очень велики, это означает, что они должны быть их собственными классами.
Обычно, когда у вас большой внутренний класс, это потому, что внутренний класс тесно связан с содержащим его классом и нуждается в доступе к его закрытым методам.
Я думаю, что это довольно субъективно, но я бы, вероятно, разделил их на отдельные файлы кода, сделав класс "host" частичным.
Делая так, вы можете получить еще больше обзора, отредактировав файл проекта, чтобы сделать группу файлов точно такой же, как классы дизайнеров в 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 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);
}