为什么/什么时候应该在.net中使用嵌套类? 或者不应该?

在Kathleen Dollard最近的博客文章中 ,她提出了在.net中使用嵌套类的一个有趣的理由。 但是,她也提到FxCop不喜欢嵌套类。 我假设编写FxCop规则的人不是愚蠢的,所以这个职位背后一定有推理,但是我一直没能find它。

当您正在嵌套的类只对封闭类有用时使用嵌套类。 例如,嵌套类允许你写(简体):

public class SortedMap { private class TreeNode { TreeNode left; TreeNode right; } } 

你可以在一个地方完整地定义你的class级,你不需要跳过任何PIMPL环节来定义你的class级是如何工作的,外部世界不需要看到你的任何实现。

如果TreeNode类是外部的,那么你必须public所有的字段,或者使用一堆get/set方法来使用它。 外界会有另一个class级污染他们的intellisense。

从Sun的Java教程:

为什么使用嵌套类? 其中有几个令人信服的理由使用嵌套类,其中包括:

  • 这是一种对仅在一个地方使用的类进行逻辑分组的方法。
  • 它增加了封装。
  • 嵌套类可以导致更易读和可维护的代码。

类的逻辑分组 – 如果一个类只对另外一个类有用,那么把它embedded到这个类中是合乎逻辑的,并把它们放在一起。 嵌套这样的“帮助类”使得它们的包更加简化。

增加封装 – 考虑两个顶级类,A和B,其中B需要访问A的成员,否则将声明为私有。 通过在类A中隐藏类B,可以将A的成员声明为私有,并且B可以访问它们。 另外,B本身可以被外界隐藏起来。 < – 这不适用于C#的嵌套类的实现,这只适用于Java。

更易读,可维护的代码 – 在顶级类中嵌套小类使代码更靠近使用位置。

完全懒惰和线程安全的单身模式

 public sealed class Singleton { Singleton() { } public static Singleton Instance { get { return Nested.instance; } } class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton instance = new Singleton(); } } 

来源: http : //www.yoda.arachsys.com/csharp/singleton.html

这取决于使用情况。 我很less会永远使用一个公共嵌套类,但总是使用私有嵌套类。 一个私有的嵌套类可以用于一个子对象,这个子对象只能在父类中使用。 一个例子是如果一个HashTable类包含一个私有的Entry对象只在内部存储数据。

如果这个类是为了被调用者使用(外部),我通常喜欢把它作为一个独立的类。

如果我理解Katheleen的文章,她build议使用嵌套类来编写SomeEntity.Collection而不是EntityCollection <SomeEntity>。 在我看来,这是有争议的方式来节省你一些打字。 我非常肯定的是,在现实世界中,应用程序集合在实现中会有一些不同,所以您将需要创build单独的类。 我认为使用类名限制其他类的范围不是一个好主意。 它污染了智力,加强了class级之间的依赖关系。 使用名称空间是控制类作用域的一种标准方法。 但是我发现像@hazzen注释这样的嵌套类的使用是可以接受的,除非你有大量的嵌套类,这是一个坏devise的标志。

除了上面列出的其他原因,还有一个原因,我不仅可以考虑使用嵌套类,而且实际上是公共嵌套类。 对于那些使用共享相同genericstypes参数的generics类的人来说,声明generics名称空间的能力将非常有用。 不幸的是.Net(或者至lessC#)不支持generics命名空间的思想。 所以为了实现相同的目标,我们可以使用generics类来实现相同的目标。 以下是与逻辑实体相关的示例类:

 public class BaseDataObject < tDataObject, tDataObjectList, tBusiness, tDataAccess > where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new() where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess> { } public class BaseDataObjectList < tDataObject, tDataObjectList, tBusiness, tDataAccess > : CollectionBase<tDataObject> where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new() where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess> { } public interface IBaseBusiness < tDataObject, tDataObjectList, tBusiness, tDataAccess > where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new() where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess> { } public interface IBaseDataAccess < tDataObject, tDataObjectList, tBusiness, tDataAccess > where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new() where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess> where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess> { } 

我们可以通过使用generics名称空间(通过嵌套类实现)来简化这些类的签名:

 public partial class Entity < tDataObject, tDataObjectList, tBusiness, tDataAccess > where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new() where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess { public class BaseDataObject {} public class BaseDataObjectList : CollectionBase<tDataObject> {} public interface IBaseBusiness {} public interface IBaseDataAccess {} } 

然后,通过使用Erik van Brakel在前面的评论中build议的部分类,可以将这些类分离为单独的嵌套文件。 我推荐使用像NestIn这样的Visual Studio扩展来支持嵌套部分类文件。 这允许“命名空间”类文件也可以用来以类似的方式在文件夹中组织嵌套的类文件。

例如:

Entity.cs

 public partial class Entity < tDataObject, tDataObjectList, tBusiness, tDataAccess > where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new() where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess { } 

Entity.BaseDataObject.cs

 partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess> { public class BaseDataObject { public DataTimeOffset CreatedDateTime { get; set; } public Guid CreatedById { get; set; } public Guid Id { get; set; } public DataTimeOffset LastUpdateDateTime { get; set; } public Guid LastUpdatedById { get; set; } public static implicit operator tDataObjectList(DataObject dataObject) { var returnList = new tDataObjectList(); returnList.Add((tDataObject) this); return returnList; } } } 

Entity.BaseDataObjectList.cs

 partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess> { public class BaseDataObjectList : CollectionBase<tDataObject> { public tDataObjectList ShallowClone() { var returnList = new tDataObjectList(); returnList.AddRange(this); return returnList; } } } 

Entity.IBaseBusiness.cs

 partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess> { public interface IBaseBusiness { tDataObjectList Load(); void Delete(); void Save(tDataObjectList data); } } 

Entity.IBaseDataAccess.cs

 partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess> { public interface IBaseDataAccess { tDataObjectList Load(); void Delete(); void Save(tDataObjectList data); } } 

Visual Studio解决scheme资源pipe理器中的文件将按如下方式组织:

 Entity.cs + Entity.BaseDataObject.cs + Entity.BaseDataObjectList.cs + Entity.IBaseBusiness.cs + Entity.IBaseDataAccess.cs 

你会像下面这样实现通用的命名空间:

User.cs

 public partial class User : Entity < User.DataObject, User.DataObjectList, User.IBusiness, User.IDataAccess > { } 

User.DataObject.cs

 partial class User { public class DataObject : BaseDataObject { public string UserName { get; set; } public byte[] PasswordHash { get; set; } public bool AccountIsEnabled { get; set; } } } 

User.DataObjectList.cs

 partial class User { public class DataObjectList : BaseDataObjectList {} } 

User.IBusiness.cs

 partial class User { public interface IBusiness : IBaseBusiness {} } 

User.IDataAccess.cs

 partial class User { public interface IDataAccess : IBaseDataAccess {} } 

这些文件将在解决scheme资源pipe理器中进行组织,如下所示:

 User.cs + User.DataObject.cs + User.DataObjectList.cs + User.IBusiness.cs + User.IDataAccess.cs 

以上是使用外部类作为通用名称空间的简单示例。 我已经构build了包含9个或更多types参数的“通用名称空间”。 不得不保持这些types参数在所有需要知道types参数的九种types之间进行同步,特别是在添加新参数时。 通用名称空间的使用使得代码更易于pipe理和阅读。

尚未提到的嵌套类的另一个用途是genericstypes的分离。 例如,假设我们想要有一些静态类的通用系列,它们可以使用不同数量的参数的方法,以及其中一些参数的值,并使用更less的参数生成委托。 例如,希望有一个静态方法,它可以接受一个Action<string, int, double>并产生一个String<string, int> ,它将调用传递3.5的提供的action作为double ; 也可能希望有一个静态方法,它可以接受一个Action<string, int, double>并产生一个Action<string> ,传递7作为int ,传递5.3作为double 。 使用generics嵌套类,可以安排方法调用如下所示:

 MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5); MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3); 

或者,因为在每个expression式中后面的types可以被推断,即使前者不能:

 MakeDelegate<string,int>.WithParams(theDelegate, 3.5); MakeDelegate<string>.WithParams(theDelegate, 7, 5.3); 

使用嵌套的genericstypes可以确定哪些代理适用于整体types描述的哪些部分。

嵌套类可以用于以下需求:

  1. 数据的分类
  2. 当主类的逻辑复杂,你觉得你需要从属对象来pipe理类
  3. 当你认为这个阶级的状态和存在完全依赖于封闭的阶级

我经常使用嵌套类来隐藏实现细节。 Eric Lippert在这里的答案就是一个例子:

 abstract public class BankAccount { private BankAccount() { } // Now no one else can extend BankAccount because a derived class // must be able to call a constructor, but all the constructors are // private! private sealed class ChequingAccount : BankAccount { ... } public static BankAccount MakeChequingAccount() { return new ChequingAccount(); } private sealed class SavingsAccount : BankAccount { ... } } 

这种模式通过使用generics变得更好。 看到这个问题两个很酷的例子。 所以我最终写作

 Equality<Person>.CreateComparer(p => p.Id); 

代替

 new EqualityComparer<Person, int>(p => p.Id); 

另外我可以有一个通用的Equality<Person>列表,但不是EqualityComparer<Person, int>

 var l = new List<Equality<Person>> { Equality<Person>.CreateComparer(p => p.Id), Equality<Person>.CreateComparer(p => p.Name) } 

在哪里

 var l = new List<EqualityComparer<Person, ??>>> { new EqualityComparer<Person, int>>(p => p.Id), new EqualityComparer<Person, string>>(p => p.Name) } 

不可能。 这是从父类inheritance的嵌套类的好处。

另一种情况(相同的性质 – 隐藏实现)是当你想要一个类的成员(字段,属性等)只能访问一个类:

 public class Outer { class Inner //private class { public int Field; //public field } static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class } 

由于nawfal提到了抽象工厂模式的实现,所以这个代码可以被用来实现基于抽象工厂模式的类集合模式。

我喜欢嵌套对于一个类是唯一的exception,即。 那些从来没有从其他地方抛出的。

例如:

 public class MyClass { void DoStuff() { if (!someArbitraryCondition) { // This is the only class from which OhNoException is thrown throw new OhNoException( "Oh no! Some arbitrary condition was not satisfied!"); } // Do other stuff } public class OhNoException : Exception { // Constructors calling base() } } 

这有助于保持您的项目文件整洁,而不是充满了一百个粗糙的小exception类。

请记住,您将需要testing嵌套的类。 如果它是私人的,你将无法孤立地进行testing。

尽pipe如此,您可以将其内部化, 并与InternalsVisibleTo属性结合使用 。 然而,这与仅仅为了testing目的而将私人领域内部化,这是我认为不好的自我logging。

所以,你可能只想实现低复杂度的私有嵌套类。