【问题标题】:Using Inner classes in C#在 C# 中使用内部类
【发布时间】:2010-10-22 17:05:18
【问题描述】:

关于 C# 中内部类的使用和结构的最佳实践是什么。

例如,如果我有一个非常大的基类和两个大的内部类,我应该将它们拆分为单独的(部分类)代码文件还是将它们保留为一个非常大且笨拙的代码文件?

拥有一个带有公共继承内部类的抽象类也是不好的做法吗?

【问题讨论】:

    标签: c# inner-classes


    【解决方案1】:

    通常我会为以下两个目的之一保留内部类:

    1. 从其父类派生的公共类,其中父类是具有一个或多个抽象方法的抽象基实现,每个子类都是服务于特定实现的实现。 在阅读框架设计和指南后,我看到它被标记为“避免”,但是我在类似于枚举的场景中使用它——尽管这也可能给人留下不好的印象

      李>
    2. 内部类是私有的,是业务逻辑单元,或者以其他方式与其父类紧密耦合,从而在被任何其他类消费或使用时从根本上被破坏。

    对于所有其他情况,我尝试将它们保持在与其使用者/逻辑父级相同的命名空间和相同的可访问性级别——通常使用的名称比“主”类不太友好。

    在大型项目中,您会惊讶地发现自己最初构建强耦合组件的频率仅仅是因为它的首要或主要目的使其看起来合乎逻辑——但是除非您有非常好的或技术上的理由来锁定它将其隐藏起来,那么暴露该类以便其他组件可以使用它几乎没有什么害处。

    编辑请记住,即使我们谈论的是子类,它们也应该或多或少是设计良好且松散耦合的组件。即使它们是私有的并且对外部世界不可见,在类之间保持最小的“表面积”也将极大地简化代码的可维护性,以便将来扩展或更改。

    【讨论】:

    • 这:“当被任何其他类消耗或使用时,根本就坏了。”
    【解决方案2】:

    我手头没有这本书,但框架设计指南建议使用public 内部类,只要客户端不必引用类名即可。 private 内部类很好:没有人会注意到这些。

    不好: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

    好: listView.Items.Add(...);

    关于你的大类:通常值得将这样的东西分成更小的类,每个类都有一个特定的功能。一开始很难打破它,但我预测它会让你以后的生活更轻松......

    【讨论】:

    • 本书中的一些观点(第 102 页)...嵌套类型应谨慎使用;嵌套类型最适合建模其父类型的实现细节;最终用户应该很少需要声明嵌套类型,并且几乎从不实例化一个;不要使用嵌套类型进行分组——使用命名空间;避免公共嵌套类型
    【解决方案3】:

    通常内部类应该是私有的,并且只能由包含它们的类使用。如果它们的内部类非常大,则表明它们应该是自己的类。

    通常当你有一个大的内部类时,这是因为内部类与它的包含类紧密耦合,并且需要访问它的私有方法。

    【讨论】:

      【解决方案4】:

      我认为这是相当主观的,但我可能会通过将“主机”类设为部分将它们拆分为单独的代码文件。

      通过这样做,您可以通过editing the project file 获得更多概览,以使文件组就像 Windows 窗体中的设计器类一样。我想我见过一个 Visual Studio 插件,它会自动为您执行此操作,但我不记得在哪里。

      编辑:
      经过一番查看,我发现了用于执行此操作的 Visual Studio 加载项,称为 VSCommands

      【讨论】:

      • 如果您将它们命名为 MyClass.cs、MyClass.subportion.cs、MyClass.anotherbit.cs、MyClass.lastoneipromise.cs 等,它们不会在 Visual Studio 中自动执行此操作吗?
      • 我没有亲自做过,但也许他们已经在 Visual Studio 2008 中实现了它?
      • 我刚刚测试过,Visual Studio 不会自动执行此操作。
      • 如果您在一个文件(例如 pclass.cs)中执行 public partial class pclass {} 并在另一个文件(例如 sclass.cs)中执行 public partial class pclass { class subclass {} },Visual Studio 2010理解它们是同一个类(在 MSIL 中,它们的编译方式就像它们在同一个文件中一样)。不过,我知道 VS2010 是在这篇文章之后发布的。
      【解决方案5】:

      仅关于如何构造这样的野兽......

      您可以使用分部类来拆分主类和嵌套类。当您这样做时,建议您适当地命名文件,以便一目了然。

      // main class in file Outer.cs
      namespace Demo
      {
        public partial class Outer
        {
           // Outer class
        }
      }
      
      // nested class in file Outer.Nested1.cs
      namespace Demo
      {
        public partial class Outer
        {
          private class Nested1
          {
            // Nested1 details
          }
        }
      }
      

      以同样的方式,您经常会在自己的文件中看到(显式)接口。例如Outer.ISomeInterface.cs 而不是编辑器默认的#regioning 他们。

      然后您的项目文件结构开始看起来像

      /Project/Demo/ISomeInterface.cs /Project/Demo/Outer.cs /Project/Demo/Outer.Nested1.cs /Project/Demo/Outer.ISomeInterface.cs

      通常,当我们这样做时,它是针对 Builder 模式的变体。

      【讨论】:

        【解决方案6】:

        我个人喜欢每个文件有一个类,内部类作为该文件的一部分。我相信内部类通常(几乎总是)应该是私有的,并且是类的实现细节。将它们放在单独的文件中会使事情变得混乱,IMO。

        在这种情况下,使用代码区域包裹内部类并隐藏它们的细节对我来说效果很好,并且可以防止文件难以处理。代码区域保持内部类“隐藏”,因为它是私有的实现细节,所以我觉得很好。

        【讨论】:

          【解决方案7】:

          我个人使用内部类来封装一些仅在类内部使用的概念和操作。这样我就不会污染该类的非公共 api 并保持 api 干净和紧凑。

          您可以利用部分类将这些内部类的定义移动到不同的文件中以便更好地组织。 VS 不会自动为您分组部分类文件,除了一些模板化项目,如 ASP.NET、WinForm 表单等。您需要编辑项目文件并在其中进行一些更改。您可以查看其中的现有分组之一以了解它是如何完成的。我相信有一些宏可以让您在解决方案资源管理器中为您分组部分类文件。

          【讨论】:

            【解决方案8】:

            在我看来,如果需要,内部类应该保持较小,并且只由该类在内部使用。如果您在 .NET 框架上使用 Relfector,您会发现它们被大量用于此目的。

            如果你的内部类变得太大,我肯定会以某种方式将它们移到单独的类/代码文件中,如果只是为了可维护性。我必须支持一些现有的代码,有人认为在内部类中使用内部类是个好主意。它导致了一个运行 4 到 5 层深度的内部类层次结构。不用说代码是难以理解的,需要很长时间才能理解你在看什么。

            【讨论】:

              【解决方案9】:

              这里是一个嵌套类的实际示例,可以让您了解它们的使用(添加了一些单元测试)

              namespace CoreLib.Helpers
              {
                  using System;
                  using System.Security.Cryptography;
              
                  public static class Rnd
                  {
                      private static readonly Random _random = new Random();
              
                      public static Random Generator { get { return _random; } }
              
                      static Rnd()
                      {
                      }
              
                      public static class Crypto
                      {
                          private static readonly RandomNumberGenerator _highRandom = RandomNumberGenerator.Create();
              
                          public static RandomNumberGenerator Generator { get { return _highRandom; } }
              
                          static Crypto()
                          {
                          }
              
                      }
              
                      public static UInt32 Next(this RandomNumberGenerator value)
                      {
                          var bytes = new byte[4];
                          value.GetBytes(bytes);
              
                          return BitConverter.ToUInt32(bytes, 0);
                      }
                  }
              }
              
              [TestMethod]
              public void Rnd_OnGenerator_UniqueRandomSequence()
              {
                  var rdn1 = Rnd.Generator;
                  var rdn2 = Rnd.Generator;
                  var list = new List<Int32>();
                  var tasks = new Task[10];
                  for (var i = 0; i < 10; i++)
                  {
                      tasks[i] = Task.Factory.StartNew((() =>
                      {
                          for (var k = 0; k < 1000; k++)
                          {
                              lock (list)
                              {
                                  list.Add(Rnd.Generator.Next(Int32.MinValue, Int32.MaxValue));
                              }
                          }
                      }));
                  }
                  Task.WaitAll(tasks);
                  var distinct = list.Distinct().ToList();
                  Assert.AreSame(rdn1, rdn2);
                  Assert.AreEqual(10000, list.Count);
                  Assert.AreEqual(list.Count, distinct.Count);
              }
              
              [TestMethod]
              public void Rnd_OnCryptoGenerator_UniqueRandomSequence()
              {
                  var rdn1 = Rnd.Crypto.Generator;
                  var rdn2 = Rnd.Crypto.Generator;
                  var list = new ConcurrentQueue<UInt32>();
                  var tasks = new Task[10];
                  for (var i = 0; i < 10; i++)
                  {
                      tasks[i] = Task.Factory.StartNew((() =>
                      {
                          for (var k = 0; k < 1000; k++)
                          {
                                  list.Enqueue(Rnd.Crypto.Generator.Next());
                          }
                      }));
                  }
                  Task.WaitAll(tasks);
                  var distinct = list.Distinct().ToList();
                  Assert.AreSame(rdn1, rdn2);
                  Assert.AreEqual(10000, list.Count);
                  Assert.AreEqual(list.Count, distinct.Count);
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-05-12
                • 1970-01-01
                • 1970-01-01
                • 2011-03-10
                • 1970-01-01
                相关资源
                最近更新 更多