在C#中使用内部类

关于C#中内部类的使用和结构的最佳实践是什么?

例如,如果我有一个非常大的基类和两个大的内部类,我应该将它们分成单独的(部分类)代码文件,还是将它们作为一个非常大的笨重的代码文件?

拥有一个公共inheritance的内部类的抽象类也是不好的做法吗?

通常我会为以下两个目的之一保留内部类:

  1. 从其父类派生的公共类,其中父类是具有一个或多个抽象方法的抽象基础实现,每个子类是为特定实现提供服务的实现。 在阅读框架设计和指南之后,我看到它被标记为“避免”,但我在类似于枚举的场景中使用它 – althogh也可能给人留下不好的印象

  2. 内部类是私有的,是业务逻辑的单元,或者以其他方式紧密耦合到它们的父类,它们在被任何其他类消费或使用时从根本上被破坏。

对于所有其他情况,我尝试将它们保持在与其使用者/逻辑父级相同的命名空间和相同的可访问级别 – 通常使用比“主”类稍微不友好的名称。

在大型项目中,您会惊讶地发现自己最初构建一个强耦合组件的频率是因为它的第一个或主要目的使它看起来合乎逻辑 – 但除非您有非常好或技术上的理由将其锁定并隐藏从视线开始,暴露课程几乎没有什么害处,以便其他组件可以消耗它。

编辑请记住,即使我们谈论的是子类,它们应该是设计得很好或松散耦合的组件。 即使它们是私有的并且对于外部世界是不可见的,在类之间保持最小的“表面区域”将极大地简化代码的可维护性,以便将来扩展或更改。

我没有这本书,但框架设计指南建议使用public内部类,只要客户不必引用类名。 private内部阶级很好:没有人会注意到这些。

错误: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

好: listView.Items.Add(...);

关于你的大class:通常有必要将这样的东西分成更小的类,每个类都有一个特定的function。 最初很难打破它,但我预测它会让你的生活更轻松……

通常,内部类应该是私有的,并且只能由包含它们的类使用。 如果他们的内部类非常大,则表明他们应该是他们自己的类。

通常当你有一个很大的内部类时,它是因为内部类与它的包含类紧密耦合,需要访问其私有方法。

我认为这是相当主观的,但我可能会通过将“host”类局部化而将它们拆分为单独的代码文件。

通过这样做,您可以通过编辑项目文件来获得更多概述,使文件组与Windows窗体中的设计器类一样。 我想我已经看过一个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。

您的项目文件结构然后开始看起来像

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

通常,当我们这样做时,它是针对Builder模式的变体。

我个人喜欢每个文件有一个类,内部类作为该文件的一部分。 我认为内部类通常(几乎总是)是私有的,并且是类的实现细节。 将它们放在一个单独的文件中会让人感到困惑,IMO。

在这种情况下,使用代码区域来包裹内部类并隐藏它们的细节对我来说很有效,并且使文件难以使用。 代码区域保持内部类“隐藏”,因为它是私有的实现细节,这对我来说没问题。

我个人使用内部类来封装一些仅在内部使用的概念和操作。 这样我就不会污染那个class级的’非公开api,并保持api清洁和紧凑。

您可以利用部分类将这些内部类的定义移动到不同的文件中,以便更好地组织。 除了一些模板化项目(如ASP.NET,WinForm表单等)之外,VS不会自动为您分组部分类文件。您需要编辑项目文件并在其中进行一些更改。 您可以查看其中一个现有分组,了解它是如何完成的。 我相信有一些宏允许您在解决方案资源管理器中为您分组部分类文件。

在我看来,如果需要的话,内部类应该保持较小并且仅在该类内部使用。 如果在.NET框架上使用Relfector,您会看到它们仅用于此目的。

如果你的内部类太大了,我肯定会以某种方式将它们移到单独的类/代码文件中,如果只是为了可维护性。 我必须支持一些现有的代码,有人认为在内部类中使用内部类是个好主意。 它导致内部类层次结构运行深度为4到5级。 不用说代码是不可穿透的,需要很长时间才能理解你在看什么。

这里看一个嵌套类的实际例子,它可以让你知道它们的用途(添加了一些unit testing)

 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(); 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(); 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); }