为什么要使用嵌套类?

25
这个stackoverflow答案中,评论者提到“私有嵌套类”可能非常有用,所以我在像这样的文章中阅读了关于嵌套类如何工作的说明,但没有解释为什么要使用它们。
我想我可以使用私有嵌套类来创建属于大型类的小辅助类,但通常我会需要另一个类的辅助类,所以我只需额外付出一点努力来(1)使嵌套类成为非嵌套类或(2)将其公开,然后在其前缀上使用外部类,这似乎是额外的工作,并没有为首先拥有嵌套类增加任何附加价值。因此,总体而言,我真的看不出嵌套类的用例,除了可能将类保持更有组织性的分组,但这也违背了我喜欢的每个文件一个类的清晰度。 您如何使用嵌套类使您的代码更易管理、可读性更高、效率更高?

1
嵌套类的使用案例是确保在Eric Lippert有关编译器的博客文章中讨论的问题从一开始看起来并不像表面那么简单... - AakashM
这是一个非常好的问题,但由于它没有明确的答案,将其设为社区维基页面会更有益。 - Malfist
13个回答

32
当您需要一个仅在类内有意义的帮助类时,请使用嵌套类;特别是当嵌套类可以利用外部类的私有实现细节时。你已经回答了自己的问题。
您认为嵌套类是无用的,这个论点同样适用于私有方法是无用的:私有方法可能在类外有用,因此您必须将其设为内部方法。内部方法可能在程序集外有用,因此您会将其设置为公共方法。因此,所有方法都应该是公共的。如果您认为这是一个糟糕的论点,那么为什么您要为类而不是方法做出相同的论点呢?
我经常编写嵌套类,因为我经常需要将功能封装在一个帮助类中,该类在类外没有意义,并且可以使用外部类的私有实现细节。例如,我编写编译器。我最近编写了一个名为SemanticAnalyzer的类,用于对语法分析树进行语义分析。其中一个嵌套类是LocalScopeBuilder。当我不分析语法分析树的语义时,我何时需要构建本地范围?从未。该类完全是语义分析器的实现细节。我计划添加更多的嵌套类,例如NullableArithmeticAnalyzer和OverloadResolutionAnalyzer,这些类在类外也没有用处,但我想将语言规则封装在这些特定的类中。
人们还使用嵌套类构建诸如迭代器或比较器之类的东西 - 这些东西在类外没有意义,并且通过一个公共的接口进行公开。
我经常使用的一种模式是具有扩展其外部类的私有嵌套类:
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 { ... }

等等,嵌套类与工厂模式结合得很好。这里的BankAccount是各种银行账户类型的工厂,所有这些类型都可以使用BankAccount的私有实现细节。但是第三方无法创建自己的类型EvilBankAccount来扩展BankAccount。


3
我认为我之前从未意识到嵌套类的价值,其中一个原因是正如Henk在这里指出的那样,有用的不是嵌套类本身,而是私有嵌套类。例如,你在答案中使用的所有示例都是私有嵌套类。所以在阅读了上面所有答案后,我现在可以列举出许多私有嵌套类的好用例子,但没有公共、内部或公共内部嵌套类的例子。 - Edward Tanguay
2
@Edward:的确,框架设计准则建议公共嵌套类并不是一个好主意。虽然合法,但并不是良好的风格。有点像公共字段一样。需要公共嵌套类的情况很少。 - Eric Lippert
1
是的,混淆部分来自于教程教授如何制作私有和公共嵌套类,好像它们在您的编程中具有相等的价值。 - Edward Tanguay
我经常使用工厂方法,而没有使用嵌套类,下次会尝试一下,很不错。 - Edward Tanguay
你的代码示例还展示了类簇模式,该模式基于抽象工厂模式。 - diimdeep
显示剩余2条评论

11
将一个接口返回给调用者以隐藏其实现。
public class Outer
{
    private class Inner : IEnumerable<Foo>
    {
        /* Presumably this class contains some functionality which Outer needs
         * to access, but which shouldn't be visible to callers 
         */
    }

    public IEnumerable<Foo> GetFoos()
    {
        return new Inner();
    }
}

7

私有的辅助类是一个很好的例子。

例如,用于后台线程的状态对象。没有强制要求将这些类型公开。将它们定义为私有嵌套类型似乎是一种非常干净的处理方式。


1
没有什么比打开一个包并看到50个类的情况更糟糕(好吧,还有一些其他的事情),其中一半是XMLMarshaler和Helper。 :-( - corsiKa
6
是的。我最常使用私有嵌套类型来处理表单中的状态转换。这样做的优点是,状态可以访问和修改表单控件,而无需将它们公开/内部化,并且它将实际的状态转换细节与主要表单代码分离。 - Dougc

6

当两个绑定值(如哈希表中)在内部不够用,但在外部足够时,我会使用它们。然后创建一个具有需要存储的属性的嵌套类,并通过方法仅公开其中几个。

我认为这很有意义,因为如果没有其他人要使用它,为什么要创建外部类呢?这就没有意义。

至于每个文件一个类,您可以使用partial关键字创建部分类,这是我通常做的。


2
+1 从来没有想过只为嵌套类使用partial,很有趣。 - Edward Tanguay

6

最近我遇到的一个引人注目的例子是许多数据结构中的Node类。例如,一个Quadtree需要知道它在节点中存储数据的方式,但你的代码中的其他部分不应该关心这些细节。


6
我发现它们非常有用的几种情况:
1. 管理复杂的私有状态,例如由Interpolator类使用的InterpolationTriangle。 Interpolator的用户不需要知道它是使用Delauney三角剖分实现的,也肯定不需要知道三角形,因此数据结构是一个私有嵌套类。
2. 正如其他人提到的那样,您可以通过接口公开类使用的数据,而不揭示类的完整实现。嵌套类还可以访问外部类的私有状态,这使您能够编写紧密耦合的代码,而不公开这种紧密耦合(甚至对于程序集的其余部分也是如此)。
3. 我遇到过一些框架期望类从某个基类派生(例如WPF中的DependencyObject),但您希望您的类继承自不同的基类。通过使用从框架基类派生的私有嵌套类,可以与框架进行互操作。因为嵌套类可以访问私有状态(只需在创建时向其传递父级的“this”),所以您可以基本上使用它来通过组合实现贫穷的多重继承。

3
你的第三点关于“穷人的多重继承”非常深刻,值得尝试。 - Edward Tanguay

5

我认为其他人已经很好地涵盖了公共和私有嵌套类的用例。

我没有看到的一点是回答你对每个文件一个类的担忧。您可以通过将外部类设为partial,并将内部类定义移动到单独的文件中来解决此问题。

OuterClass.cs:

namespace MyNameSpace
{
    public partial class OuterClass
    {
        // main class members here
        // can use inner class
    }
}

OuterClass.Inner.cs:

namespace MyNameSpace
{
    public partial class OuterClass
    {
        private class Inner
        {
            // inner class members here
        }
    }
}

你甚至可以利用Visual Studio的项嵌套功能,将OuterClass.Inner.cs作为OuterClass.cs的“子项”,以避免混乱你的解决方案资源管理器。

3

这种技术常用于某些场景,例如一个类从其属性或方法返回一个接口或基类类型,但具体类型是一个私有嵌套类。请考虑以下示例。

public class MyCollection : IEnumerable
{
  public IEnumerator GetEnumerator()
  {
    return new MyEnumerator();
  }

  private class MyEnumerator
  {
  }
}

2

在某些情况下,我通常会将SRP(单一职责原则)相结合。

"好吧,如果SRP是你的目标,为什么不将它们拆分成不同的类呢?" 你可以在80%的时间里这样做,但是如果创建的类对外部世界没有用处怎么办?你不希望只有你自己使用的类会使你的程序集API变得混乱。

"那么internal就是为此而设的了吗?" 当然,对于这80%的情况是这样。但是对于必须访问或修改公共类状态的内部类呢?例如,为了满足你的SRP streak而将一个类分解为一个或多个内部类的情况?你需要将所有方法和属性标记为internal,以供这些internal类使用。

"这有什么问题吗?" 对于这80%的情况没有什么问题。当然,现在你的类的内部接口被那些你之前创建的类才能使用的方法/属性所占据。现在你还需要担心团队中的其他人编写的内部代码是否会通过使用你未预期的方式来使用这些方法而破坏你的状态。

内部类可以修改其定义类型的任何实例的状态。因此,不需要向类型定义中添加成员,你的内部类就可以根据需要对它们进行操作。在100个案例中的14个案例中,这将是保持类型简洁、代码可靠/可维护和责任单一的最佳选择。


1

它们非常适合例如单例模式的实现。

我有几个地方也在使用它们来“添加”价值。我有一个多选组合框,我的内部类存储复选框的状态和数据项。不需要让全世界知道或使用这个内部类。


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