Использование внутренних классов в С#
Каковы наилучшие методы использования и структуры внутренних классов в С#.
Например, если у меня есть очень большой базовый класс и два больших внутренних класса, я должен разделить их на отдельные (неполные классы) кодовые файлы или оставить их как один очень большой громоздкий файл кода?
Также есть ли плохая практика иметь абстрактный класс с общепринятым внутренним классом?
Ответы
Ответ 1
Обычно я резервирую внутренние классы для одной из двух целей:
-
Открытые классы, которые вытекают из их родительского класса, где родительский класс представляет собой абстрактную базовую реализацию с одним или несколькими абстрактными методами, и каждый подкласс представляет собой реализацию, которая выполняет конкретную реализацию. после прочтения Framework Framework и рекомендаций. Я вижу, что это отмечено как "Избегайте", однако я использую его в сценариях, похожих на перечисления - althogh, которые, вероятно, также дают плохое впечатление.
-
Внутренние классы являются частными и являются единицами бизнес-логики или иным образом тесно связаны с их родительским классом таким образом, что они принципиально нарушаются при использовании или использовании любым другим классом.
Для всех остальных случаев я стараюсь держать их в том же пространстве имен и том же уровне доступности, что и их потребительский/логический родительский элемент - часто с именами, которые немного менее дружелюбны, чем "основной" класс.
В крупных проектах вы удивляетесь, как часто вы можете сначала создать сильно связанный компонент только потому, что это первая или основная цель делает его логичным - однако, если у вас нет очень хорошей или технической причины блокировать его вниз и спрячьте это от зрения, тогда мало вреда в том, чтобы подвергать класс, чтобы другие компоненты могли его потреблять.
Изменить Имейте в виду, что, хотя мы говорим о подклассах, они должны быть более или менее хорошо разработанными и слабо связанными компонентами. Даже если они являются частными и невидимыми для внешнего мира, сохраняя минимальную "площадь поверхности" между классами, значительно облегчит ремонтопригодность вашего кода для будущего расширения или изменения.
Ответ 2
У меня нет книги, но в Руководстве по дизайну Framework предлагается использовать внутренние классы public
, пока клиенты не должны ссылаться на имя класса. private
внутренние классы в порядке: никто не заметит их.
Плохо: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();
Хорошо: listView.Items.Add(...);
Относительно вашего большого класса: обычно стоит разделить что-то подобное на более мелкие классы, каждый из которых имеет одну конкретную функциональность. Сначала трудно сломать его, но я предсказываю, что это облегчит вашу жизнь позже...
Ответ 3
Обычно внутренние классы должны быть частными и использоваться только классом, который их содержит. Если их внутренние классы очень велики, они предполагают, что они должны быть их собственными классами.
Обычно, когда у вас есть большой внутренний класс, потому что внутренний класс тесно связан с ним, содержащим класс, и ему нужен доступ к его частным методам.
Ответ 4
Я думаю, что это довольно субъективно, но я, вероятно, разделил бы их в отдельных файлах кода, сделав частичный класс "хозяин".
Таким образом, вы можете получить еще больший обзор редактирование файла проекта, чтобы сделать группу файлов так же, как классы дизайнеров в Windows Формы. Я думаю, что я видел добавление Visual Studio, которое делает это автоматически для вас, но я не помню, где.
EDIT:
После некоторого поиска я обнаружил, что надстройка Visual Studio для этого называется VSCommands
Ответ 5
Относительно только того, как структурировать такого зверя...
Вы можете использовать частичные классы для разделения основного класса и вложенных классов. Когда вы это сделаете, вам рекомендуется называть файлы соответствующим образом, чтобы было очевидно, что происходит.
// 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.
Ответ 6
Мне лично нравится иметь один класс для каждого файла и внутренние классы как часть этого файла. Я считаю, что внутренние классы обычно (почти всегда) являются частными и представляют собой деталь реализации этого класса. Наличие их в отдельном файле смущает вещи, ИМО.
Использование областей кода для обертывания внутренних классов и скрыть их данные хорошо работает для меня, в этом случае, и с этим трудно работать. Области кода сохраняют внутренний класс "скрытым", и поскольку это частная реализация, это прекрасно со мной.
Ответ 7
Я лично использую внутренние классы для инкапсуляции некоторых понятий и операций, используемых только внутри класса. Таким образом, я не загрязняю неклассический api этого класса и сохраняю api чистым и компактным.
Вы можете использовать частичные классы, чтобы переместить определение этих внутренних классов в другой файл для лучшей orgnanization. VS не автоматически группирует файлы частичного класса для вас, за исключением некоторых шаблонизированных элементов, таких как формы ASP.NET, WinForm и т.д. Вам нужно будет отредактировать файл проекта и внести в него некоторые изменения. Вы можете посмотреть на одну из существующих группировок, чтобы посмотреть, как это делается. Я считаю, что есть несколько макросов, которые позволяют группировать частичные файлы классов для вас в проводнике решений.
Ответ 8
По моему мнению, внутренние классы, если они вообще нужны, должны быть небольшими и использоваться только внутри этого класса. Если вы используете Relfector на платформе .NET, вы увидите, что они использовали много для этой цели.
Если ваши внутренние классы становятся слишком большими, я бы определенно переместил их в отдельные классы/кодовые файлы так или иначе, хотя бы для удобства обслуживания. Я должен поддерживать некоторый существующий код, где кто-то думал, что это хорошая идея использовать внутренние классы внутри внутренних классов. Это привело к тому, что внутренняя иерархия классов с глубиной 4-5 уровней. Излишне говорить, что код непроницаем и требует времени, чтобы понять, на что вы смотрите.
Ответ 9
Вот пример практического примера вложенного класса, который может дать вам представление об их использовании (добавлено несколько unit test)
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);
}