【问题标题】:Why/when should you use nested classes in .net? Or shouldn't you?为什么/何时应该在 .net 中使用嵌套类?还是不应该?
【发布时间】:2010-09-08 02:40:55
【问题描述】:

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

【问题讨论】:

标签: .net class nested fxcop


【解决方案1】:

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

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

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

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

【讨论】:

  • 补充一点:您还可以在单​​独的文件中使用部分类来更好地管理您的代码。将内部类放在一个单独的文件中(本例中为 SortedMap.TreeNode.cs)。这应该保持你的代码干净,同时也保持你的代码分开:)
  • 在某些情况下,如果嵌套类用于公共 API 的返回类型或容器类的公共属性,则需要将其设为公共或内部。我不确定这是否是一个好习惯。在这种情况下,将嵌套类拉出容器类之外可能更有意义。 .Net 框架中的 System.Windows.Forms.ListViewItem.ListViewSubItem 类就是这样一个例子。
【解决方案2】:

From Sun's Java Tutorial:

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

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

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

增强封装——考虑两个顶级类 A 和 B,其中 B 需要访问 A 的成员,否则这些成员将被声明为私有。通过将类 B 隐藏在类 A 中,可以将 A 的成员声明为私有的,并且 B 可以访问它们。此外,B 本身可以对外界隐藏。

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

【讨论】:

  • 这并不适用,因为您不能像在 Java 中那样从 C# 中的封闭类访问实例变量。只能访问静态成员。
  • 但是,如果将封闭类的实例传递给嵌套类,则嵌套类可以通过该实例变量完全访问所有成员......所以真的,就像 Java 使实例变量一样隐式的,而在 C# 中,您必须使其显式。
  • @Alex 不,不是,在 Java 中,嵌套类在实例化时实际上会捕获父类实例——除此之外,这意味着它可以防止父类被垃圾收集。这也意味着没有父类就无法实例化嵌套类。所以不,这些根本不一样。
  • @TomášZato 我的描述非常贴切,真的。在 Java 的嵌套类中实际上有一个隐式父实例变量,而在 C# 中,您必须显式地将实例交给内部类。正如您所说,这样做的结果是 Java 的内部类必须有一个父实例,而 C# 没有。在任何情况下,我的主要观点是 C# 的内部类也可以访问其父级私有字段和属性,但必须显式传递父级实例才能做到这一点。
【解决方案3】:

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

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://csharpindepth.com/Articles/Singleton

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

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

      例如:

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

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

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

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          我经常使用嵌套类来隐藏实现细节。 An example from Eric Lippert's answer here:

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

          这种模式在使用泛型后会变得更好。请参阅this question 了解两个很酷的示例。所以我最终写了

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

          而不是

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

          我也可以有一个Equality&lt;Person&gt; 的通用列表,但不是EqualityComparer&lt;Person, int&gt;

          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
          }
          

          【讨论】:

            【解决方案8】:

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

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

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

            【讨论】:

              【解决方案9】:

              嵌套类可用于以下需求:

              1. 数据分类
              2. 当主类的逻辑比较复杂,感觉需要从属对象来管理类时
              3. 当你认为类的状态和存在完全依赖于封闭类时

              【讨论】:

                【解决方案10】:

                由于nawfal 提到了抽象工厂模式的实现,该代码可以扩展为基于抽象工厂模式的Class Clusters pattern

                【讨论】:

                  【解决方案11】:

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

                  例如:

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

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

                  【讨论】:

                    【解决方案12】:

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

                    不过,您可以将其设为内部,in conjunction with the InternalsVisibleTo attribute。但是,这与仅出于测试目的而在内部创建私有字段相同,我认为这是错误的自我记录。

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

                    【讨论】:

                      【解决方案13】:

                      在这种情况下是的:

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

                      【讨论】:

                      • 为什么?为您的解决方案中的代码添加一些上下文,以帮助未来的读者理解您的答案背后的原因。
                      【解决方案14】:

                      基于我对这个概念的理解 当类在概念上相互关联时,我们可以使用此功能。 我的意思是它们中的一些是我们业务中的一个完整项目,就像 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();
                              }
                      
                      
                          }
                      

                      【讨论】:

                        猜你喜欢
                        • 2013-03-29
                        • 2012-03-31
                        • 2021-05-08
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-09-08
                        • 2012-02-10
                        • 2015-05-11
                        相关资源
                        最近更新 更多