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

Eri*_*ins 91 .net fxcop nested class

Kathleen Dollard最近的博客文章中,她提出了一个在.net中使用嵌套类的有趣理由.但是,她还提到FxCop不喜欢嵌套类.我假设编写FxCop规则的人并不愚蠢,所以必须在该位置背后有推理,但我无法找到它.

haz*_*zen 94

当您要嵌套的类仅对封闭类有用时,请使用嵌套类.例如,嵌套类允许您编写类似(简化)的内容:

public class SortedMap {
    private class TreeNode {
        TreeNode left;
        TreeNode right;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在一个地方完成您的课程的完整定义,您不必跳过任何PIMPL箍来定义您的课程的工作方式,外部世界不需要查看您的实施内容.

如果TreeNode类是外部的,您可能必须创建所有字段public或使用一堆get/set方法来使用它.外面的世界会有另一个阶级污染他们的智力感知.

  • 要添加到此:您还可以在单​​独的文件中使用部分类来更好地管理代码.将内部类放在单独的文件中(在本例中为SortedMap.TreeNode.cs).这应该保持你的代码干净,同时保持你的代码分离:) (44认同)

Est*_*aya 16

来自Sun的Java教程:

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

  • 这是一种逻辑分组仅在一个地方使用的类的方法.
  • 它增加了封装.
  • 嵌套类可以使代码更具可读性和可维护性.

类的逻辑分组 - 如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的.嵌套这样的"帮助类"使得它们的包更加简化.

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

更易读,可维护的代码 - 在顶级类中嵌套小类会使代码更接近于使用它的位置.

  • 但是,如果将封闭类的实例传递给嵌套类,则嵌套类通过该实例变量可以完全访问所有成员...所以实际上,它就像Java使实例变量隐式,而在C#中你必须明确表达. (4认同)
  • 这实际上并不适用,因为您无法像在 Java 中那样从 C# 中的封闭类访问实例变量。只能访问静态成员。 (2认同)
  • @TomášZato 我的描述非常贴切,真的。在 Java 的嵌套类中实际上有一个隐式父实例变量,而在 C# 中,您必须显式地将实例传递给内部类。正如您所说,这样做的结果是 Java 的内部类必须有一个父实例,而 C# 则没有。在任何情况下,我的主要观点是 C# 的内部类也可以访问其父私有字段和属性,但必须显式传递父实例才能执行此操作。 (2认同)

kay*_*one 9

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Chr*_*ail 5

这取决于用途.我很少会使用Public嵌套类,但始终使用Private嵌套类.私有嵌套类可用于仅在父级内部使用的子对象.例如,如果HashTable类包含仅在内部存储数据的私有Entry对象.

如果该类要由调用者(外部)使用,我通常喜欢将它作为一个单独的独立类.


Tyr*_*son 5

除了上面列出的其他原因之外,还有一个原因,我不仅可以考虑使用嵌套类,还可以考虑使用公共嵌套类.对于那些使用共享相同泛型类型参数的多个泛型类的人来说,声明泛型命名空间的能力非常有用.不幸的是,.Net(或至少C#)不支持通用名称空间的概念.因此,为了实现相同的目标,我们可以使用泛型类来实现相同的目标.以下示例类与逻辑实体相关:

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>
{
}
Run Code Online (Sandbox Code Playgroud)

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

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 {}

}
Run Code Online (Sandbox Code Playgroud)

然后,通过使用Erik van Brakel在之前的注释中建议的部分类,您可以将类分成单独的嵌套文件.我建议使用像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
{
}
Run Code Online (Sandbox Code Playgroud)

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

    }

}
Run Code Online (Sandbox Code Playgroud)

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

    }

}
Run Code Online (Sandbox Code Playgroud)

Entity.IBaseBusiness.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseBusiness
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}
Run Code Online (Sandbox Code Playgroud)

Entity.IBaseDataAccess.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseDataAccess
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}
Run Code Online (Sandbox Code Playgroud)

然后,visual studio解决方案资源管理器中的文件将按如下方式组织:

Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs
Run Code Online (Sandbox Code Playgroud)

您将实现通用命名空间,如下所示:

User.cs

public
partial class   User
:
                Entity
                <
                    User.DataObject, 
                    User.DataObjectList, 
                    User.IBusiness, 
                    User.IDataAccess
                >
{
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

User.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}
Run Code Online (Sandbox Code Playgroud)

User.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}
Run Code Online (Sandbox Code Playgroud)

User.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}
Run Code Online (Sandbox Code Playgroud)

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

User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs
Run Code Online (Sandbox Code Playgroud)

以上是使用外部类作为通用名称空间的简单示例.我在过去构建了包含9个或更多类型参数的"通用名称空间".必须在所有需要知道类型参数的九种类型中保持这些类型参数同步是繁琐的,特别是在添加新参数时.通用命名空间的使用使得代码更易于管理和读取.