C#中的私有内部类-为什么不经常使用?

79

我相对来说是C#的新手,每次开始工作在一个C#项目上时(我只曾经在接近成熟的C#项目上工作过),我都会想为什么没有内部类?

也许我不理解它们的目的。对我而言,内部类——至少是私有内部类——看起来很像Pascal / Modula-2 / Ada中的“内部程序”:它们允许将一个主类分解成更小的部分,以便于理解。

例如:这是我经常看到的:

public class ClassA
{
   public MethodA()
   {
      <some code>
      myObjectClassB.DoSomething(); // ClassB is only used by ClassA
      <some code>
   }
}

public class ClassB
{
   public DoSomething()
   {
   }
}

由于ClassB只会在一段时间内被ClassA使用,所以我猜这段代码最好改为以下形式:

   public class ClassA
   {
      public MethodA()
      {
         <some code>
         myObjectClassB.DoSomething(); // Class B is only usable by ClassA
         <some code>
      }

      private class ClassB
      {
         public DoSomething()
         {
         }
      }
   }

我很乐意听取您对这个问题的意见 - 我是正确的吗?


1
还要注意的是,你不能在嵌套类中使用扩展方法。 - nawfal
4个回答

82

嵌套类(最好避免使用“内部”一词,因为C#中的嵌套类与Java中的内部类有所不同)确实非常有用。

还有一个模式没有被提到,那就是“更好的枚举”模式 - 这种模式甚至比Java中的枚举更加灵活:

public abstract class MyCleverEnum
{
    public static readonly MyCleverEnum First = new FirstCleverEnum();
    public static readonly MyCleverEnum Second = new SecondCleverEnum();

    // Can only be called by this type *and nested types*
    private MyCleverEnum()
    {
    }

    public abstract void SomeMethod();
    public abstract void AnotherMethod();

    private class FirstCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // First-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // First-specific behaviour here
        }
    }

    private class SecondCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // Second-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // Second-specific behaviour here
        }
    }
}

我们需要一些语言支持来自动完成其中的一些操作 - 这里有很多我没有展示的选项,比如并不是为所有值都使用嵌套类,或者为多个值使用相同的嵌套类,但给它们不同的构造函数参数。但基本上,嵌套类可以调用私有构造函数这一事实赋予了很大的权力。


12
非常聪明!但我永远不会使用它。软件书籍的作者和读者们通常都很聪明。他们思考了很多亮点,这很酷。但是软件维护人员是普通人,他们没有阅读Gamma/Skeet的经验。我的猜测是:它越聪明,可维护性就越差... - Sylvain Rodrigue
30
直到它成为一个被认可的模式,才会变得正确。就像"while ((line = streamReader.ReadLine()) != null)"一开始看起来很糟糕——在while条件中有副作用!但当它成为惯用语时,你会忽略那种感觉并欣赏其简洁性。 - Jon Skeet
2
@Sylvain:我认为这就像LINQ一样——当你习惯了它,它会增加可维护性,但一开始看起来很奇怪。但我同意,在C#中这不是一个常见的模式:( - Jon Skeet
3
@Jon,谢谢!我现在明白了。MyCleverEnum 作为枚举成员的持有者和每个枚举成员的接口,因此比其他解决方案更加简洁。使用嵌套类确保支持类对外部类隐藏。私有构造函数使该类在外部看起来像静态/单例类,在内部则像抽象基类。 - devuxer
2
@supercat,你可以在文件顶部放置一个using指令using FirstCleverEnum = MyCleverEnum.FirstCleverEnum。这可以缓解问题,但没有办法“导出”此别名。 - Marty Neal
显示剩余17条评论

31

框架设计准则是我目前找到的关于使用嵌套类的最佳规则。

以下是一个简要的总结列表:

  1. 当类型和嵌套类型之间的关系需要成员可访问性语义时,请使用嵌套类型。

  2. 不要将公共嵌套类型用作逻辑组构造。

  3. 避免使用公开暴露的嵌套类型。

  4. 如果类型有可能在包含类型外被引用,请勿使用嵌套类型。

  5. 如果需要由客户端代码实例化,则不要使用嵌套类型。

  6. 请勿将嵌套类型定义为接口的成员。


2
其中一些规则是相当明显的(例如4和5)。但您能指出为什么推荐其他规则的原因吗? - Peter Bagnall
@PeterBagnall 这是只有我一个人这样认为,还是第六个与被接受的答案相矛盾了? - jungle_mole
1
@jungle_mole 实现公共接口的嵌套类型是可以的,也很常见。但是嵌套类型不应该被公开暴露。 - Erick
@Erick 是的。我只是把两个“设计”的两个部分搞混了。说实话,这第六个原因很明显。我当时在想什么呢? - jungle_mole

13

为了让每个类都保持简单易懂、易于测试和可重用,您应该限制每个类的职责。私有内部类则违反了这一原则。它们会增加外层类的复杂性,无法进行测试并且不可重用。


简单、可测试和可重用非常棒。但是封装呢?当某些东西不应该被外部看到时,为什么要将其公开?在我的书中,内部类可以使整个事情变得更简单。而且它们是可测试的(使用反射)! - Sylvain Rodrigue
4
我会选择封装作为反对内部类的论点。内部类可以访问其外部类的私有成员! - Wim Coenen
你说得对!内部类会在它和外部类之间添加某种耦合 - 我一开始没有看到 - 抱歉。谢谢! - Sylvain Rodrigue
10
我看程序员经常使用元组等数据结构代替私有类。我肯定会争辩说私有类会导致更易于阅读和减少错误的代码。认为它违反了封装是不正确的。私有类并没有违反封装的任何规则。它被包含在一个类中,并且应该和其他外部类成员一样,具有相同的权限。 - Mark
就像所有事情一样,这取决于具体的嵌套类。例如,如果您在父类中创建一个数据传输对象(例如,为了保持方法签名的清晰),那么就没有什么需要测试的了。在我看来,这与创建私有元组没有什么不同,只是更加清晰明了。但是,如果您创建了一个具有功能的丰富嵌套类,那么您将会遇到麻烦。 - Sinaesthetic
我同意Mark的观点。我经常使用元组来封装应该被封装在外部类中的东西。后来,我转向了私有类。它们比元组更方便。它们可以具有行为和有意义的属性,而不是Item1、Item2等等。并且它们不会使外部代码膨胀。 - vllado2

3

就我个人而言,只有在需要创建对象的进程集合并可能需要对它们进行方法操作时,才会创建私有内部类。

否则,会让其他开发人员很难找到这些类,因为它们的位置不太清晰,容易引起混淆。


1
但是由于它们是私有类,除非其他开发人员必须维护外部类,否则他们不应该知道这些类。当然,在人们不了解私有内部类的情况下,这可能会成为一个问题。感谢您的答复! - Sylvain Rodrigue
17
如果这让我的开发人员感到困惑,我会寻找新的开发人员。 - DancesWithBamboo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接