100

Kathleen Dollard 的 2008 年博客文章中,她提出了在 .net 中使用嵌套类的有趣理由。然而,她也提到 FxCop 不喜欢嵌套类。我假设编写 FxCop 规则的人并不愚蠢,所以这个立场背后一定有推理,但我一直找不到。

4

14 回答 14

102

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

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

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

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

于 2008-09-07T23:12:43.133 回答
16

来自 Sun 的 Java 教程:

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

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

类的逻辑分组——如果一个类只对另一个类有用,那么将它嵌入该类并将两者放在一起是合乎逻辑的。嵌套这样的“帮助类”使它们的包更加精简。

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

更具可读性、可维护性的代码——在顶级类中嵌套小类可以使代码更接近使用它的位置。

于 2008-09-07T23:56:20.660 回答
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();
    }
}

来源:https ://cshapindepth.com/Articles/Singleton

于 2009-07-05T18:32:01.550 回答
5

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

如果该类是由调用者(外部)使用的,我通常喜欢将其设为一个单独的独立类。

于 2008-09-07T23:39:13.803 回答
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>
{
}

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

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 扩展来支持嵌套部分类文件。这允许“命名空间”类文件也可以用于在文件夹中组织嵌套的类文件。

例如:

实体.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 解决方案资源管理器中的文件将按如下方式组织:

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

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

用户.cs

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

用户数据对象.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 {}

}

用户.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

用户.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

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

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

上面是一个使用外部类作为泛型命名空间的简单示例。过去,我构建了包含 9 个或更多类型参数的“通用名称空间”。必须在所有需要知道类型参数的九种类型之间保持这些类型参数同步是很乏味的,尤其是在添加新参数时。通用命名空间的使用使代码更易于管理和可读。

于 2015-03-31T22:06:14.497 回答
3

如果我对 Katheleen 的文章理解正确,她建议使用嵌套类来编写 SomeEntity.Collection 而不是 EntityCollection<SomeEntity>。在我看来,这是一种有争议的方式来节省你的打字时间。我很确定在现实世界中的应用程序集合在实现上会有一些差异,所以无论如何你都需要创建单独的类。我认为使用类名来限制其他类的范围不是一个好主意。它污染了智能感知并加强了类之间的依赖关系。使用命名空间是控制类范围的标准方法。但是,我发现像 @hazzen 评论中使用嵌套类是可以接受的,除非您有大量嵌套类,这是设计不良的标志。

于 2008-09-07T23:24:42.590 回答
1

我经常使用嵌套类来隐藏实现细节。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 { ... }
}

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

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

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

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

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
}
于 2013-04-18T21:07:57.250 回答
1

嵌套类尚未提及的另一个用途是泛型类型的分离。例如,假设想要拥有一些通用的静态类家族,它们可以采用具有不同数量参数的方法以及其中一些参数的值,并生成具有较少参数的委托。例如,一个人希望有一个静态方法,它可以接受一个Action<string, int, double>并产生一个String<string, int>,它将调用提供的动作,将 3.5 作为double; 可能还希望有一个静态方法,它可以采用 anAction<string, int, double>并产生 an ,作为 the和作为 theAction<string>传递。使用通用嵌套类,可以安排方法调用类似于:7int5.3double

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

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

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

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

于 2013-09-02T19:27:23.263 回答
1

嵌套类可用于以下需求:

  1. 数据分类
  2. 当主类的逻辑比较复杂,感觉需要从属对象来管理类时
  3. 当您认为类的状态和存在完全取决于封闭类时
于 2014-04-27T12:51:57.840 回答
0

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

于 2013-10-31T08:10:32.147 回答
0

我喜欢嵌套单个类独有的异常,即。那些从未从任何其他地方扔过的东西。

例如:

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

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

于 2014-12-19T23:41:37.077 回答
0

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

不过,您可以将其InternalsVisibleTo属性结合使用。但是,这与仅出于测试目的在内部创建私有字段相同,我认为这是错误的自我记录。

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

于 2015-03-10T09:03:21.693 回答
0

是的,对于这种情况:

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);
        }
    }
}
于 2018-06-23T02:16:50.553 回答
0

根据我对这个概念的理解,当类在概念上相互关联时,我们可以使用这个特性。我的意思是它们中的一些是我们业务中的一个完整项目,例如存在于 DDD 世界中的实体,它们帮助聚合根对象完成其业务逻辑。

为了澄清,我将通过一个例子来展示这一点:

假设我们有两个类,比如 Order 和 OrderItem。在 order 类中,我们将管理所有 orderItems,在 OrderItem 中,我们保存有关单个订单的数据以进行澄清,您可以看到以下类:

 class Order
    {
        private List<OrderItem> _orderItems = new List<OrderItem>();

        public void AddOrderItem(OrderItem line)
        {
            _orderItems.Add(line);
        }

        public double OrderTotal()
        {
            double total = 0;
            foreach (OrderItem item in _orderItems)
            {
                total += item.TotalPrice();
            }

            return total;
        }

        // Nested class
        public class OrderItem
        {
            public int ProductId { get; set; }
            public int Quantity { get; set; }
            public double Price { get; set; }
            public double TotalPrice() => Price * Quantity;
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            Order order = new Order();

            Order.OrderItem orderItem1 = new Order.OrderItem();
            orderItem1.ProductId = 1;
            orderItem1.Quantity = 5;
            orderItem1.Price = 1.99;
            order.AddOrderItem(orderItem1);

            Order.OrderItem orderItem2 = new Order.OrderItem();
            orderItem2.ProductId = 2;
            orderItem2.Quantity = 12;
            orderItem2.Price = 0.35;
            order.AddOrderItem(orderItem2);

            Console.WriteLine(order.OrderTotal());
            ReadLine();
        }


    }
于 2020-08-03T05:49:52.953 回答