在这个stackoverflow答案 一位评论者提到“私有嵌套类”可能非常有用,因此我在 诸如此类的文章往往会解释嵌套类如何技术上发挥作用,但不会为什么使用它们。
我想我会为属于较大类的小助手类使用私有嵌套类,但通常我需要另一个类的助手类,所以我只需要付出额外的努力( 1)使嵌套类成为非嵌套类或(2)将其公开,然后使用外部类前缀访问它,这两者似乎都是额外的工作,没有任何附加值,因为首先拥有嵌套类。因此,总的来说,我真的没有看到嵌套类的用例,除了可能让类更加组织成组之外,但我这也违背了单类-我开始喜欢每个文件的清晰度。
您如何使用嵌套类来使您的代码更易于管理、可读、高效?
In this stackoverflow answer a commenter mentioned that "private nested classes" can be quite useful so I was reading about them in articles such as this one which tend to explain how nested classes function technically, but not why you would use them.
I suppose I would use private nested classes for little helper classes that belong to a larger class, but often I will need a helper class from another class and so I would just have to take the extra effort to (1) make the nested class non-nested or (2) make it public and then access it with the outer-class prefix on it, which both seems to be extra work without any added-value for having the nested class in the first place. Hence in general I really don't see a use case for nested classes, other than perhaps to keep classes a bit more organized into groups, but I that also goes against the one-class-per-file clarity that I have come to enjoy.
In what ways do you use nested classes to make your code more manageable, readable, efficient?
发布评论
评论(13)
你已经回答了你自己的问题。当您需要一个在类之外没有意义的辅助类时,请使用嵌套类;特别是当嵌套类可以利用外部类的私有实现细节时。
您认为嵌套类无用的论点也是私有方法无用的论点:私有方法可能在类外部有用,因此您必须将其设置为内部方法。内部方法可能在程序集外部有用,因此您可以将其公开。因此,所有方法都应该是公共的。如果您认为这是一个糟糕的论点,那么您对类而不是方法进行相同的论点有何不同?
我一直在创建嵌套类,因为我经常需要将功能封装在一个在类外部没有意义的帮助器中,并且可以使用外部类的私有实现细节。例如,我编写编译器。我最近编写了一个 SemanticAnalyzer 类,它对解析树进行语义分析。它的嵌套类之一是 LocalScopeBuilder。当我不分析解析树的语义时,在什么情况下需要构建本地作用域?绝不。该类完全是语义分析器的实现细节。我计划添加更多名称为 NullableArithmeticAnalyzer 和 OverloadResolutionAnalyzer 的嵌套类,这些类在类之外也没有用,但我想将语言的规则封装在这些特定的类中。
人们还使用嵌套类来构建迭代器或比较器之类的东西 - 这些东西在类之外没有任何意义,并通过众所周知的接口公开。
我经常使用的一种模式是拥有扩展其外部类的私有嵌套类:
等等。嵌套类与工厂模式配合得很好。这里BankAccount是各种类型银行账户的工厂,所有类型的银行账户都可以使用BankAccount的私有实现细节。但没有第三方可以创建自己的类型 EvilBankAccount 来扩展 BankAccount。
You've answered your own question. Use nested classes when you need a helper class that is meaningless outside the class; particularly when the nested class can make use of private implementation details of the outer class.
Your argument that nested classes are useless is also an argument that private methods are useless: a private method might be useful outside of the class, and therefore you'd have to make it internal. An internal method might be useful outside of the assembly, and therefore you'd make it public. Therefore all methods should be public. If you think that's a bad argument, then what is different about you making the same argument for classes instead of methods?
I make nested classes all the time because I am frequently in the position of needed to encapsulate functionality in a helper that makes no sense outside of the class, and can use private implementation details of the outer class. For example, I write compilers. I recently wrote a class SemanticAnalyzer that does semantic analysis of parse trees. One of its nested classes is LocalScopeBuilder. Under what circumstances would I need to build a local scope when I am not analyzing the semantics of a parse tree? Never. That class is entirely an implementation detail of the semantic analyzer. I plan to add more nested classes with names like NullableArithmeticAnalyzer and OverloadResolutionAnalyzer that are also not useful outside of the class, but I want to encapsulate rules of the language in those specific classes.
People also use nested classes to build things like iterators, or comparators - things that make no sense outside of the class and are exposed via a well-known interface.
A pattern I use quite frequently is to have private nested classes that extend their outer class:
and so on. Nested classes work very well with the factory pattern. Here BankAccount is a factory for various types of bank account, all of which can use the private implementation details of BankAccount. But no third party can make their own type EvilBankAccount that extends BankAccount.
将接口返回给要隐藏其实现的调用者。
Returning an interface to the caller whose implementation you want to hide.
私人助手类就是一个很好的例子。
例如,后台线程的状态对象。没有令人信服的理由来公开这些类型。将它们定义为私有嵌套类型似乎是处理这种情况的一种非常干净的方法。
Private helper classes is a good example.
For instance, state objects for background threads. There is no compelling reason to expose those types. Defining them as private nested types seems a quite clean way to handle the case.
当两个绑定值(例如在哈希表中)在内部不够,但在外部足够时,我使用它们。然后,我使用需要存储的属性创建一个嵌套类,并通过方法仅公开其中的一些属性。
我认为这是有道理的,因为如果没有其他人会使用它,为什么要为它创建一个外部类呢?这是没有意义的。
至于每个文件一个类,您可以使用
partial
关键字创建部分类,这就是我通常所做的。I use them when two bound values (like in a hash table) are not enough internally, but are enough externally. Then i create a nested class with the properties i need to store, and expose only a few of them through methods.
I think this makes sense, because if no one else is going to use it, why create an external class for it? It just doesn't make sense to.
As for one class per file, you can create partial classes with the
partial
keyword, which is what I usually do.我最近遇到的一个引人注目的例子是许多数据结构的
Node
类。例如,四叉树需要知道它如何将数据存储在其节点中,但代码的其他部分不应该关心。One compelling example I've run into recently is the
Node
class of many data structures. AQuadtree
, for example, needs to know how it stores the data in its nodes, but no other part of your code should care.我发现了一些非常方便的情况:
管理复杂的私有状态,例如 Interpolator 类使用的 InterpolationTriangle。 Interpolator 的用户不需要知道它是使用 Delauney 三角剖分实现的,当然也不需要了解三角形,因此数据结构是一个私有嵌套类。
正如其他人所提到的,您可以通过接口公开类使用的数据,而无需透露类的完整实现。嵌套类还可以访问外部类的私有状态,这允许您编写紧密耦合的代码,而无需公开公开(甚至在内部向程序集的其余部分公开)紧密耦合。
我遇到过一些情况,框架期望一个类从某个基类(例如 WPF 中的 DependencyObject)派生,但您希望您的类从不同的基类继承。通过使用从框架基类派生的私有嵌套类,可以与框架进行互操作。 ),您基本上可以使用它通过组合来实现穷人的多重继承。
I've found a few cases where they've been quite handy:
Management of complex private state, such as an InterpolationTriangle used by an Interpolator class. The user of the Interpolator doesn't need to know that it's implemented using Delauney triangulation and certainly doesn't need to know about the triangles, so the data structure is a private nested class.
As others have mentioned, you can expose data used by the class with an interface without revealing the full implementation of a class. Nested classes can also access private state of the outer class, which allows you to write tightly coupled code without exposing that tight coupling publicly (or even internally to the rest of the assembly).
I've run into a few cases where a framework expects a class to derive from some base class (such as DependencyObject in WPF), but you want your class to inherit from a different base. It's possible to inter-operate with the framework by using a private nested class that descends from the framework base class. Because the nested class can access private state (you just pass it the parent's 'this' when you create it), you can basically use this to implement a poor man's multiple inheritance via composition.
我认为其他人已经很好地涵盖了公共和私有嵌套类的用例。
我没有看到的一点是您对每个文件一个类的担忧的答案。您可以通过使外部类部分化并将内部类定义移动到单独的文件来解决此问题。
OuterClass.cs:
OuterClass.Inner.cs:
您甚至可以利用 Visual Studio 的项目嵌套使 OuterClass.Inner.cs 成为 OuterClass.cs 的“子级”,以避免使您的解决方案资源管理器混乱。
I think others have covered the use cases for public and private nested classes well.
One point I haven't seen made was an answer your concern about one-class-per-file. You can solve this by making the outer class partial, and move the inner class definition to a separate file.
OuterClass.cs:
OuterClass.Inner.cs:
You could even make use of Visual Studio's item nesting to make OuterClass.Inner.cs a 'child' of OuterClass.cs, to avoid cluttering your solution explorer.
使用此技术的一种非常常见的模式是类从其属性或方法之一返回接口或基类类型,但具体类型是私有嵌套类的场景。考虑以下示例。
One very common pattern where this technique is used is in scenarios where a class returns an interface or base class type from one of its properties or methods, but the concrete type is a private nested class. Consider the following example.
当我在某些情况下需要结合 SRP(单一责任主体)时,我通常会这样做。
“那么,如果 SRP 是您的目标,为什么不将它们分成不同的类呢?” 80% 的情况下您会这样做,但是如果您创建的类对外界无用,该怎么办? ?您不希望只有您才使用的类会扰乱程序集的 API。
“嗯,这不是
内部
的用途吗?”当然。大约 80% 的情况。但是对于必须访问或修改公共类的状态的内部类呢?例如,为了满足您的 SRP 连续性,该类别被分解为一个或多个内部类别?您还必须将这些internal
类使用的所有方法和属性标记为internal
。“这有什么问题吗?”什么都没有。大约 80% 的情况。当然,现在您正在用仅对您之前创建的那些类有用的方法/属性来混淆类的内部接口。现在您必须担心团队中编写内部代码的其他人不会以您意想不到的方式使用这些方法来扰乱您的状态。
内部类可以修改定义它们的类型的任何实例的状态。因此,无需将成员添加到类型的定义中,您的内部类就可以根据需要对其进行操作。在 100 种情况中大约有 14 种情况下,这将是保持类型干净、代码可靠/可维护以及职责单一的最佳选择。
I usually do it when I need a combination of SRP (Single Responsibility Principal) in certain situations.
"Well, if SRP is your goal, why not split them into different classes?" You will do this 80% of the time, but what about situations where the classes you create are useless to the outside world? You don't want classes that only you will use to clutter your assembly's API.
"Well, isn't that what
internal
is for?" Sure. For about 80% of these cases. But what about internal classes who must access or modify the state of public classes? For example, that class which was broken up into one or more internal classes to satisfy your SRP streak? You would have to mark all the methods and properties for use by theseinternal
classes asinternal
as well."What's wrong with that?" Nothing. For about 80% of these cases. Of course, now you're cluttering the internal interface of your classes with methods/properties that are only of use to those classes which you created earlier. And now you have to worry about other people on your team writing internal code won't mess up your state by using those methods in ways that you hadn't expected.
Internal classes get to modify the state of any instance of the type in which they are defined. So, without adding members to the definition of your type, your internal classes can work on them as needed. Which, in about 14 cases in 100, will be your best bet to keep your types clean, your code reliable/maintainable, and your responsibilities singular.
例如,它们非常适合单例模式的实现。
我也有几个地方使用它们来“增加”价值。我有一个多选组合框,其中我的内部类还存储复选框的状态和数据项。世界不需要知道/使用这个内部类。
They are really nice for, as an example, an implementation of the singleton pattern.
I have a couple of places where I am using them to "add" value, as well. I have a multi-select combobox where my internal class stores the state of the checkbox and the data item as well. no need for the world to know about/use this internal class.
私有匿名嵌套类对于 GUI 中的事件处理程序至关重要。
如果某个类不是另一个类导出的 API 的一部分,则必须将其设为私有。否则你暴露的东西会超出你的预期。 “百万美元漏洞”就是一个例子。大多数程序员对此都太松懈了。
彼得
Private anonymous nested classes are essential for event handlers in the GUI.
If some class is not part of the API another class exports, it must be made private. Otherwise you are exposing more than you intend. The "million dollar bug" was an example of this. Most programmers are too slack about this.
Peter
这个问题被标记为 C#,所以我不确定这是否有趣,但在 COM 中,当 C++ 类实现多个 COM 接口时,您可以使用内部类来实现接口……本质上您将它用于组合而不是多重继承。
此外,在 MFC 和其他技术中,您可能需要控件/对话框有一个放置目标类,这除了作为嵌套类之外没有什么意义。
The question is tagged C# so I'm not sure this is of interest, but in COM you can use inner classes to implement interfaces when a class C++ implements multiple COM interfaces... essentially you use it for composition rather than multiple-inheritance.
Additionally in MFC and perhaps other technologies you might need your control/dialog to have a drop-target class, which makes little sense other than as a nested class.
如果对象需要返回一些有关其状态的抽象信息,则私有嵌套类可能比较合适。例如,如果 Fnord 支持“保存上下文”和“恢复上下文”方法,则让“保存上下文”函数返回 Fnord.SavedContext 类型的对象可能会很有用。类型访问规则并不总是最有帮助的;例如,似乎很难允许 Fnord 访问 Fnord.SavedContext 的属性和方法,而不使这些属性和方法对外部人员可见。另一方面,可以让 Fnord.CreateSaveContext 只需创建一个 New Fnord.SaveContext,并将 Fnord 作为参数(因为 Fnord.SaveContext 可以访问 Fnord 的内部),并且 Fnord.LoadContextFrom() 可以调用 Fnord.SaveContext.RestoreContextTo ()。
If it is necessary for an object to return some abstract information about its state, a private nested class may be suitable. For example, if an Fnord supports "save context" and "restore context" methods, it may be useful to have the "save context" function return an object of type Fnord.SavedContext. Type access rules aren't always the most helpful; for example, it seems difficult to allow Fnord to access properties and methods of a Fnord.SavedContext without making such properties and methods visible to outsiders. On the other hand, one could have Fnord.CreateSaveContext simply create a New Fnord.SaveContext with the Fnord as a parameter (since Fnord.SaveContext can access the internals of Fnord), and Fnord.LoadContextFrom() can call Fnord.SaveContext.RestoreContextTo().