[英]Using Inner classes in C#
关于C#中内部类的使用和结构的最佳实践是什么?
例如,如果我有一个非常大的基类和两个大的内部类,我应该将它们分成单独的(部分类)代码文件,还是将它们作为一个非常大的笨重的代码文件?
具有抽象类的公共继承内部类也是不好的做法吗?
通常我会为两个目的之一保留内部类:
从其父类派生的公共类,其中父类是具有一个或多个抽象方法的抽象基础实现,每个子类是为特定实现提供服务的实现。 在阅读框架设计和指南之后,我看到它被标记为“避免”,但我在类似于枚举的场景中使用它 - althogh也可能给人留下不好的印象
内部类是私有的,是业务逻辑的单元,或者以其他方式紧密耦合到它们的父类,它们在被任何其他类消费或使用时从根本上被破坏。
对于所有其他情况,我尝试将它们保持在与其使用者/逻辑父代相同的名称空间和相同的可访问性级别 - 通常使用比“主”类稍微不友好的名称。
在大型项目中,您会惊讶地发现自己最初构建一个强耦合组件的频率是因为它的第一个或主要目的使它看起来合乎逻辑 - 但除非您有非常好或技术上的理由将其锁定并隐藏从视线开始,暴露课程几乎没有什么害处,以便其他组件可以消耗它。
编辑请记住,即使我们谈论的是子类,它们应该是设计得很好或松散耦合的组件。 即使它们是私有的,也是外部世界不可见的,在类之间保持最小的“表面区域”将极大地简化代码的可维护性,以便将来扩展或更改。
我没有这本书,但框架设计指南建议使用public
内部类,只要客户不必引用类名。 private
内部阶级很好:没有人会注意到这些。
错误: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();
好: listView.Items.Add(...);
关于你的大班:通常值得将这样的东西分成更小的类,每个类都有一个特定的功能。 最初很难打破它,但我预测它会让你以后的生活变得更轻松......
通常,内部类应该是私有的,并且只能由包含它们的类使用。 如果他们的内部类非常大,则表明他们应该是他们自己的类。
通常当你有一个很大的内部类时,因为内部类与它的包含类紧密耦合,需要访问其私有方法。
我认为这是相当主观的,但我可能会通过使“主机”类部分而将它们拆分为单独的代码文件。
通过这样做,您可以通过编辑项目文件来获得更多概述,使文件组与Windows窗体中的设计器类一样。 我想我已经看过一个Visual Studio插件,可以自动为你做这个,但我不记得在哪里。
编辑:
经过一番观察,我找到了一个名为VSCommands的Visual Studio加载项
仅关于如何构建这样的野兽......
您可以使用部分类来拆分主类和嵌套类。 当您这样做时,建议您适当地命名文件,以便明显发生了什么。
// 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。
在这种情况下,使用代码区域来包装内部类并隐藏它们的细节对我来说很有效,并且使文件难以使用。 代码区域保持内部类“隐藏”,因为它是一个私有的实现细节,这对我来说没问题。
我个人使用内部类来封装一些仅在内部使用的概念和操作。 这样我就不会污染那个班级的非公开api并保持api干净和紧凑。
您可以利用部分类将这些内部类的定义移动到不同的文件中,以便更好地组织。 除了一些模板化的项目(如ASP.NET,WinForm表单等)之外,VS不会自动为您分组部分类文件。您需要编辑项目文件并在那里进行一些更改。 您可以查看其中一个现有分组,了解它是如何完成的。 我相信有一些宏允许您在解决方案资源管理器中为您分组部分类文件。
这里看一个嵌套类的实际例子,它可以让你知道它们的用途(添加了一些单元测试)
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);
}
在我看来,如果需要的话,内部类应该保持较小并且仅在该类内部使用。 如果在.NET框架上使用Relfector,您会看到它们仅用于此目的。
如果你的内部类太大了,我肯定会以某种方式将它们移到单独的类/代码文件中,只是为了可维护性。 我必须支持一些现有的代码,有人认为在内部类中使用内部类是个好主意。 它导致内部类层次结构运行深度为4到5级。 不用说代码是不可穿透的,需要很长时间才能理解你在看什么。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.