人们想要使用嵌套类的原因是什么?

在这个stackoverflow回答中,一位评论者提到“ 私有嵌套类”非常有用,所以我在这篇文章中阅读它们,这些文章倾向于解释嵌套类在技术上如何起作用,但不是为什么要使用它们。

我想我会将私有嵌套类用于属于更大类的小助手类 ,但是我经常需要来自另一个类的辅助类,所以我只需要额外的努力(1)使嵌套类非-nested或(2)使其公开,然后使用外部类前缀访问它,这两者似乎都是额外的工作,没有任何附加值,因为首先使用嵌套类。 因此,一般来说, 我真的没有看到嵌套类的用例 ,除了可能将类更多地组织成组,但我也违背了我已经享受的每个文件的一级清晰度。

您以何种方式使用嵌套类来使您的代码更易于管理,更易读,更高效?

你已经回答了自己的问题。 当你需要一个在类外没有意义的辅助类时,使用嵌套类; 特别是当嵌套类可以使用外部类的私有实现细节时。

您嵌套类无用的论点也是私有方法无用的参数:私有方法在类之外可能有用,因此您必须将其设置为内部方法。 内部方法在程序集之外可能很有用,因此您可以将其公之于众。 因此,所有方法都应公开。 如果你认为这是一个不好的论点,那么你为类而不是方法创建相同的参数有什么不同?

我一直在创建嵌套类,因为我经常处于需要将函数封装在一个在类之外没有意义的帮助器的位置,并且可以使用外部类的私有实现细节。 例如,我编写编译器。 我最近编写了一个类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的私人实施细节。 但是没有第三方可以自己创建扩展BankAccount类型的EvilBankAccount。

将接口返回给要隐藏其实现的调用方。

 public class Outer { private class Inner : IEnumerable { /* Presumably this class contains some functionality which Outer needs * to access, but which shouldn't be visible to callers */ } public IEnumerable GetFoos() { return new Inner(); } } 

私人助手类就是一个很好的例子。

例如,后台线程的状态对象。 揭露这些类型没有令人信服的理由。 将它们定义为私有嵌套类型似乎是处理案例的一种非常简洁的方法。

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

我认为这是有道理的,因为如果没有其他人会使用它,为什么要为它创建一个外部类? 它只是没有意义。

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

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

“私有嵌套类”非常有用

请注意该句中的私密 。 不建议使用公共嵌套类型。

就像大多数答案已经注意到的那样:每个类本身都是一个小软件项目。 有时候需要招募助手类。 但这不是一个值得努力的东西,你仍然不希望课程变得太大或太复杂。

我发现了一些他们非常方便的案例:

  1. 管理复杂的私有状态,例如Interpolator类使用的InterpolationTriangle。 Interpolator的用户不需要知道它是使用Delauney三角测量实现的,当然也不需要知道三角形,因此数据结构是一个私有嵌套类。

  2. 正如其他人所提到的,您可以使用接口公开类使用的数据,而不会泄露类的完整实现。 嵌套类还可以访问外部类的私有状态,这允许您编写紧密耦合的代码,而不会公开(或甚至内部到程序集的其余部分)紧密耦合。

  3. 我遇到了一些框架需要从某个基类派生类(例如WPF中的DependencyObject)的情况,但是您希望您的类inheritance自不同的基类。 通过使用从框架基类inheritance的私有嵌套类,可以与框架进行交互操作。 因为嵌套类可以访问私有状态(你只是在创建它时将父级的’this’传递给它),你基本上可以使用它来通过组合实现穷人的多重inheritance。

使用此技术的一种非常常见的模式是在类从其属性或方法之一返回接口或基类类型的情况下,但具体类型是私有嵌套类。 请考虑以下示例。

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

我通常在某些情况下需要SRP(单一责任委托人)的组合时这样做。

“好吧,如果SRP是你的目标,为什么不将它们分成不同的类? ”你将在80%的时间内完成这项任务,但是你创建的课程对外界来说无用的情况呢? 您不希望只使用的类会使程序集的API混乱。

“那么,这不是internal的意思吗?” 当然。 约占这些案件的80%。 但是内部类必须访问或修改公共类的状态呢? 例如,该类被分解为一个或多个内部类以满足您的SRP条纹? 您必须将这些internal类使用的所有方法和属性标记为internal类。

“这有什么问题?” 没有。 约占这些案件的80%。 当然,现在您使用仅对您之前创建的类使用的方法/属性使类的内部接口混乱。 现在你不得不担心团队中其他人编写内部代码不会以你没想到的方式使用这些方法来破坏你的状态。

内部类可以修改定义它们的任何类型实例的状态。 因此,如果不向类型定义添加成员,您的内部类可以根据需要处理它们。 其中,在100个中的大约14个案例中,最好的办法是保持您的类型清洁,您的代码可靠/可维护,以及您的责任是单一的。

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

我没有看到的一点是对每个文件一类的关注。 您可以通过使外部类成为局部来解决此问题,并将内部类定义移动到单独的文件中。

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的“子”,以避免混乱您的解决方案资源管理器。

作为一个例子,它们非常适合单例模式的实现。

我有几个地方,我用它们来“增加”价值。 我有一个多选combobox,我的内部类存储复选框的状态和数据项。 世界上没有必要知道/使用这个内部课程。

私有匿名嵌套类对于GUI中的事件处理程序至关重要。

如果某个类不是另一个类导出的API的一部分,则必须将其设为私有。 否则你暴露的比你想要的多。 “百万美元的臭虫”就是一个例子。 大多数程序员对此都太懒散了。

彼得

如果对象需要返回有关其状态的一些抽象信息,则私有嵌套类可能是合适的。 例如,如果Fnord支持“保存上下文”和“恢复上下文”方法,则使用“保存上下文”函数返回Fnord.SavedContext类型的对象可能很有用。 类型访问规则并不总是最有帮助的; 例如,似乎很难允许Fnord访问Fnord.SavedContext的属性和方法,而不会使外部人员看到这些属性和方法。 另一方面,可以让Fnord.CreateSaveContext创建一个New Fnord.SaveContext,以Fnord作为参数(因为Fnord.SaveContext可以访问Fnord的内部),而Fnord.LoadContextFrom()可以调用Fnord.SaveContext.RestoreContextTo ()。

问题是标记为C#所以我不确定这是否有意义,但是在COM中,当C ++类实现多个COM接口时,你可以使用内部类来实现接口……实际上你将它用于组合而不是多重inheritance。

此外,在MFC和其他技术中,您可能需要您的控件/对话框才能有一个drop-target类,除了作为嵌套类之外没什么意义。