关于类:为什么/何时应该在.net中使用嵌套类? 难道不是吗?

关于类:为什么/何时应该在.net中使用嵌套类? 难道不是吗?

Why/when should you use nested classes in .net? Or shouldn't you?

在Kathleen Dollard的2008年博客文章中,她提出了一个有趣的理由在.net中使用嵌套类。 但是,她还提到FxCop不喜欢嵌套类。 我假设编写FxCop规则的人并不愚蠢,因此在该职位后面必须有推理,但我找不到它。


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

1
2
3
4
5
6
public class SortedMap {
    private class TreeNode {
        TreeNode left;
        TreeNode right;
    }
}

您可以在一个地方对类进行完整定义,而不必跳过任何PIMPL箍来定义类的工作方式,并且外界也无需查看任何实现。

如果TreeNode类是外部类,则必须将所有字段都设为public或使用一堆get/set方法来使用它。外部世界将有另一类污染他们的智力。


从Sun的Java教程中:

为什么要使用嵌套类?
使用嵌套类有许多令人信服的原因,其中包括:

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

类的逻辑分组-如果一个类仅对其他一个类有用,则将其嵌入该类并将两者保持在一起是合乎逻辑的。嵌套此类"帮助程序类"可使它们的程序包更加简化。

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

更具可读性和可维护性的代码-将顶级类嵌套在小类中可以使代码更靠近使用位置。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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>
{
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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在先前的注释中建议的局部类,可以将这些类分离为单独的嵌套文件。我建议使用像NestIn这样的Visual Studio扩展来支持嵌套部分类文件。这允许"命名空间"类文件也可以用于以类似方式在文件夹中组织嵌套的类文件。

例如:

Entity.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

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

}

Entity.IBaseDataAccess.cs

1
2
3
4
5
6
7
8
9
10
11
partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

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

}

然后,Visual Studio解决方案资源管理器中的文件将组织如下:

1
2
3
4
5
Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs

然后您将实现通用名称空间,如下所示:

User.cs

1
2
3
4
5
6
7
8
9
10
11
12
public
partial class   User
:
                Entity
                <
                    User.DataObject,
                    User.DataObjectList,
                    User.IBusiness,
                    User.IDataAccess
                >
{
}

User.DataObject.cs

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

User.IBusiness.cs

1
2
3
4
5
6
partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

User.IDataAccess.cs

1
2
3
4
5
6
partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

这些文件将在解决方案资源管理器中按如下方式组织:

1
2
3
4
5
User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs

上面是使用外部类作为通用名称空间的简单示例。我过去建立了包含9个或更多类型参数的"通用名称空间"。必须使所有类型参数之间的所有九种类型参数都保持同步是乏味的,尤其是在添加新参数时。通用名称空间的使用使该代码更加易于管理和可读。


这取决于用法。我很少使用公共嵌套类,但一直都使用私有嵌套类。私有嵌套类可用于仅在父级内部使用的子对象。例如,如果HashTable类包含私有Entry对象以仅在内部存储数据,则为该示例。

如果该类是供调用者(外部)使用的,则我通常喜欢将其作为单独的独立类。


如果我理解Katheleen的文章正确,她建议使用嵌套类来编写SomeEntity.Collection而不是EntityCollection 。我认为这是为您节省输入时间的有争议的方法。我非常确定,在现实世界中,应用程序集合在实现上会有所不同,因此无论如何您都需要创建单独的类。我认为使用类名来限制其他类范围不是一个好主意。它会污染智能感知并增强类之间的依赖关系。使用名称空间是控制类范围的标准方法。但是我发现使用@hazzen注释中的嵌套类是可以接受的,除非您有大量的嵌套类,这表明设计不好。


嵌套类可用于以下需求:

  • 数据分类
  • 当主类的逻辑很复杂并且您感觉需要下级对象来管理类时
  • 当您知道类的状态和存在完全取决于封闭的类时

  • 嵌套类尚未提及的另一个用途是泛型类型的隔离。例如,假设一个人希望拥有一些静态类的通用类,这些类可以采用带有各种参数的方法以及其中一些参数的值,并生成带有较少参数的委托。例如,一个人希望拥有一个静态方法,该方法可以采用Action并产生一个String,该方法将调用提供的动作并传递3.5作为double;可能还希望有一种静态方法,该方法可以采用Action并产生Action,并通过7作为int5.3作为double。使用通用嵌套类,可以安排方法调用如下:

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

    或者,因为即使前者不能,也可以推断出每个表达式中的后者类型:

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

    使用嵌套的泛型类型可以分辨出哪些委托适用于整个类型描述的哪些部分。


    我经常使用嵌套类来隐藏实现细节。埃里克·利珀特(Eric Lippert)的答案示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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 { ... }
    }

    使用泛型时,这种模式会变得更好。有关两个很酷的示例,请参见此问题。所以我最后写了

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

    代替

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

    我也可以有Equality的通用列表,但不能有EqualityComparer

    1
    2
    3
    4
    5
    var l = new List<Equality<Person>>
            {
             Equality<Person>.CreateComparer(p => p.Id),
             Equality<Person>.CreateComparer(p => p.Name)
            }

    在哪里

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

    不可能。这就是从父类继承嵌套类的好处。

    另一种情况(性质相同-隐藏实现)是当您只想让一个类的成员(字段,属性等)可访问时:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    }

    是的,在这种情况下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class Join_Operator
    {

        class Departamento
        {
            public int idDepto { get; set; }
            public string nombreDepto { get; set; }
        }

        class Empleado
        {
            public int idDepto { get; set; }
            public string nombreEmpleado { get; set; }
        }

        public void JoinTables()
        {
            List<Departamento> departamentos = new List<Departamento>();
            departamentos.Add(new Departamento { idDepto = 1, nombreDepto ="Arquitectura" });
            departamentos.Add(new Departamento { idDepto = 2, nombreDepto ="Programación" });

            List<Empleado> empleados = new List<Empleado>();
            empleados.Add(new Empleado { idDepto = 1, nombreEmpleado ="John Doe." });
            empleados.Add(new Empleado { idDepto = 2, nombreEmpleado ="Jim Bell" });

            var joinList = (from e in empleados
                            join d in departamentos on
                            e.idDepto equals d.idDepto
                            select new
                            {
                                nombreEmpleado = e.nombreEmpleado,
                                nombreDepto = d.nombreDepto
                            });
            foreach (var dato in joinList)
            {
                Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
            }
        }
    }

    请记住,您需要测试嵌套类。如果它是私有的,那么您将无法单独对其进行测试。

    但是,您可以将其与InternalsVisibleTo属性一起设置为内部。但是,这与仅出于测试目的而将私有字段设为内部相同,我认为这是不好的自我文档。

    因此,您可能只想实现涉及低复杂度的私有嵌套类。


    我喜欢嵌套单个类唯一的异常,即。从来没有从其他地方扔过的东西。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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()
        }
    }

    这有助于使您的项目文件保持整洁,并且不会充满一百个粗俗的小异常类。


    正如nawfal提到的"抽象工厂"模式的实现一样,可以扩展该代码以实现基于"抽象工厂"模式的"类集群"模式。


    推荐阅读

      有趣简单的电脑游戏|电脑趣味游戏

      有趣简单的电脑游戏|电脑趣味游戏,,电脑趣味游戏 办公室游戏排行榜前十如下: 1、杜拉拉升职记手游(这是由苏州好玩友网络推出的一款

      百度博客搜索如何提交博客网址

      百度博客搜索如何提交博客网址,百度,中国,提交,博客,硅谷,核心技术,  在博客搜索首页上点击&ldquo;提交我的博客&rdquo;,在提交框中输入您的博

      佳能 5D Mark II如何拍摄有趣全景照片

      佳能 5D Mark II如何拍摄有趣全景照片,全景照片,照片,画面,调整,图像,操作,  佳能 5D Mark II拍摄有趣全景照片的方法如下:镜头的状态利用TS-E

      百度博客搜索Ping服务如何使用

      百度博客搜索Ping服务如何使用,百度,服务,中国,博客,地址,通知,  博客搜索Ping服务的使用方法:你可以采取手动通知和自动通知两种方式使用ping

      开始博客

      开始博客,博客,技术,一直想开一个技术博客,想把自己做的东西及遇到的问题总结归纳下来,可是比较懒一直没有开成。看师兄每隔一段时间便会总

      8个有趣实用iPhone短信技巧

      8个有趣实用iPhone短信技巧,短信,消息,输入,选择,设置,权限, 大家都用过iPhone发短信,但是你有完全发挥iMessage的全部功能吗?我们总结了

      拍一拍有趣后缀文字怎么设置

      拍一拍有趣后缀文字怎么设置,设置,文字,后缀,界面,用户,微信,拍一拍对于很多用户相对来说已经不陌生了,因为很多用户在使用微信的时候都会去进行

      方兴东 博客中国创始人

      方兴东 博客中国创始人,互联网,中国,论坛,计算机世界,数字,青年,基本资料:中文名:方兴东民族:汉出生地:浙江义乌出生日期:1969年职业:互联网毕业院校:

      新浪博客怎么加关注

      新浪博客怎么加关注,博客,新浪博客,登录,部落,网络日志,点击,新浪博客怎么加关注:在所想要加为关注的对象的博客里面,在博客左上方博主的个人资料