为啥我需要使用 C# 嵌套类 [重复]

Posted

技术标签:

【中文标题】为啥我需要使用 C# 嵌套类 [重复]【英文标题】:Why Would I Ever Need to Use C# Nested Classes [duplicate]为什么我需要使用 C# 嵌套类 [重复] 【发布时间】:2010-11-08 03:44:31 【问题描述】:

我试图了解 C# 中的嵌套类。我知道嵌套类是在另一个类中定义的类,我不明白为什么我需要这样做。

【问题讨论】:

我想知道还有多少其他问题的主题行完全相同?来吧,丹! 关于 C# 嵌套类是什么? John,我同意之前有人问过这个问题,但是对“C# 嵌套类”的搜索和右侧的“相关”面板都没有直接重复。归咎于 SO 搜索:- 你是对的。关于它们有很多,但不是“为什么”。我找到的最接近的是***.com/questions/454218/…,你必须知道inner 和nested 的意思是一样的。也许不是搜索,但也许这实际上是这个级别的第一个。 虽然我真的明白“重复问题”标记的意思,但仍然无法抗拒内心的情感驱动力来分享我的印象,这里的人非常高兴拍摄“重复”标记。虽然这些人可能比我更好地理解编程语言——但不知何故,他们似乎在正确解释英语方面存在问题,尤其是当菜鸟提出问题时。我很高兴看到你们两个人足够关心了解一个人在面对他的问题时的知识水平差异 - 很高兴有你们在这里。 【参考方案1】:

我特别喜欢的一种模式是将嵌套类与工厂模式结合起来:

public abstract class BankAccount

  private BankAccount()  // prevent third-party subclassing.
  private sealed class SavingsAccount : BankAccount  ... 
  private sealed class ChequingAccount : BankAccount  ... 
  public static BankAccount MakeSavingAccount()  ... 
  public static BankAccount MakeChequingAccount()  ... 

通过像这样嵌套类,我使第三方无法创建自己的子类。我可以完全控制在任何银行账户对象中运行的所有代码。我所有的子类都可以通过基类共享实现细节。

【讨论】:

@EricLippert 在这种情况下如何调用基类的构造函数,因为它是私有的?如果无法调用基类构造函数,如何创建派生类实例?抱歉,如果这与帖子无关,但我只是想了解代码及其用途 @EricLippert 好的,嵌套成员可以访问包含类型可访问的成员,明白了 对。 Private 表示在类型中可访问。嵌套成员在类型中定义。【参考方案2】:

目的通常只是限制嵌套类的范围。与普通类相比,嵌套类具有private 修饰符的额外可能性(当然还有protected)。

基本上,如果您只需要在“父”类中使用此类(就范围而言),则通常将其定义为嵌套类是合适的。如果这个类可能需要在没有程序集/库的情况下使用,那么用户将其定义为单独的(兄弟)类通常更方便,无论是否存在任何概念关系两个班级之间。尽管在技术上可以创建嵌套在 public 父类中的 public 类,但在我看来,这很少是合适的实现方式。

【讨论】:

事实上,.NET Framework 指南明确建议不要创建公共嵌套类。 @Henk:我认为马克指的是这个页面:msdn.microsoft.com/en-us/library/tdz1bea9(VS.71).aspx 另一个 MS 不遵守自己建议的示例:请参阅 System.Windows.Forms.ListView 嵌套类! @Henk:实际上,第二行声明“应该很少使用公共嵌套类型”。但是,我不确定它所指的“两种”条件(只有一个要点)。 关于此的更好的 MSDN 文章 - msdn.microsoft.com/en-us/library/ms229027.aspx【参考方案3】:

嵌套类可以具有privateprotectedprotected internal 访问修饰符以及publicinternal

例如,您正在实现返回IEnumerator<T> 对象的GetEnumerator() 方法。消费者不会关心对象的实际类型。他们所知道的只是它实现了该接口。您要返回的类没有任何直接用途。您可以将该类声明为 private 嵌套类并返回它的一个实例(这实际上是 C# 编译器实现迭代器的方式):

class MyUselessList : IEnumerable<int> 
    // ...
    private List<int> internalList;
    private class UselessListEnumerator : IEnumerator<int> 
        private MyUselessList obj;
        public UselessListEnumerator(MyUselessList o) 
           obj = o;
        
        private int currentIndex = -1;
        public int Current 
           get  return obj.internalList[currentIndex]; 
        
        public bool MoveNext()  
           return ++currentIndex < obj.internalList.Count;
        
    
    public IEnumerator<int> GetEnumerator() 
        return new UselessListEnumerator(this);
    

【讨论】:

此外,我发现 foreach 的内部运作方式绝对令人惊叹。谢谢! 我非常感谢这个例子。我认为对于许多 C# 新人来说,有很多隐藏的假设和未描述的过程。例如,这里有四个隐藏接口协同工作:IEnumerable、IEnumerable、IEnumerator 和 IEnumerator。假定父类是一个带有存储数字的 List 对象。并且“foreach”在遍历列表时会调用 GetEnumerator。错过了太多复杂性......【参考方案4】:

我不明白为什么我需要这样做

我认为你永远需要这样做。给定一个像这样的嵌套类...

class A

  //B is used to help implement A
  class B
  
    ...etc...
  
  ...etc...

...您始终可以将内部/嵌套类移动到全局范围,就像这样...

class A

  ...etc...


//B is used to help implement A
class B

  ...etc...

但是,当 B 仅用于帮助实现 A 时,使 B 成为内部/嵌套类有两个优点:

它不会污染全局范围(例如,可以看到 A 的客户端代码甚至不知道 B 类存在) B 的方法隐式可以访问 A 的私有成员;而如果 B 没有嵌套在 A 中,则 B 将无法访问 A 的成员,除非这些成员是内部的或公共的;但随后将这些成员设为内部或公共也会将它们暴露给其他类(不仅仅是 B);因此,将 A 的这些方法保留为私有,并通过将 B 声明为嵌套类来让 B 访问它们。如果您了解 C++,这就像说在 C# 中所有嵌套类都自动成为它们所在类的“朋友”(并且,将类声明为嵌套类是唯一的方法)在 C# 中声明友谊,因为 C# 没有 friend 关键字)。

当我说 B 可以访问 A 的私有成员时,这是假设 B 具有对 A 的引用;它经常这样做,因为嵌套类通常是这样声明的......

class A

  //used to help implement A
  class B
  
    A m_a;
    internal B(A a)  m_a = a; 
    ...methods of B can access private members of the m_a instance...
  
  ...etc...

...并使用这样的代码从 A 的方法构造...

//create an instance of B, whose implementation can access members of self
B b = new B(this);

您可以在 Mehrdad 的回复中看到一个示例。

【讨论】:

优点只有第1项,如果排除它,可以使用protected,这样“使用private字段”就变成了事物本身 嵌套类不一定是子类(可以使用protected)。例如,外部类可能是某种容器,而嵌套类可能是该容器的迭代器:迭代器想要访问容器的成员数据/实现细节。【参考方案5】:

公共嵌套成员也有很好的用途......

嵌套类可以访问外部类的私有成员。因此,在创建比较器(即实现 IComparer 接口)时,这是正确方法的一个场景。

在此示例中,FirstNameComparer 可以访问私有 _firstName 成员,如果该类是一个单独的类,则它不会...

public class Person

    private string _firstName;
    private string _lastName;
    private DateTime _birthday;

    //...
    
    public class FirstNameComparer : IComparer<Person>
    
        public int Compare(Person x, Person y)
        
            return x._firstName.CompareTo(y._firstName);
        
    

【讨论】:

除此之外,FirstNameComparer 类应该是静态的,Compare 方法也是如此。 @erike:如果您像我一样实现 IComparer 接口,则不会...... 我想你是对的! 您可以将此场景与私有嵌套一起使用并创建静态方法以在***类中公开 IComparer 的实例。因为在这种情况下,嵌套类对其他类没有用,因此请私下创建实例,而不是将控制权交给其他类【参考方案6】:

有时实现一个从类中返回的接口很有用,但该接口的实现应该对外界完全隐藏。

举个例子 - 在 C# 中添加 yield 之前,实现枚举器的一种方法是将枚举器的实现作为私有类放在集合中。这将提供对集合成员的轻松访问,但外部世界不需要/查看如何实现的详细信息。

【讨论】:

这有一个额外的好处,即使用这种实现接口的代码不能偷偷地将接口转换回实现类,因为它是嵌套的和私有的,所以它不能被引用。 @jerryjvl 这实际上非常重要! +1 肯定是因为它使代码更加安全。【参考方案7】:

嵌套类对于实现不应暴露的内部细节非常有用。如果您使用 Reflector 检查 Dictionary 或 Hashtable 等类,您会发现一些示例。

【讨论】:

【参考方案8】:

也许这是什么时候使用嵌套类的一个很好的例子?

// ORIGINAL
class ImageCacheSettings  
class ImageCacheEntry  
class ImageCache

    ImageCacheSettings mSettings;
    List<ImageCacheEntry> mEntries;

还有:

// REFACTORED
class ImageCache

    Settings mSettings;
    List<Entry> mEntries;

    class Settings 
    class Entry 

PS:我没有考虑应该应用哪些访问修饰符(私有、受保护、公共、内部)

【讨论】:

不幸的是,当这样做时,public Settings Settings get; set; 的约定消失了。

以上是关于为啥我需要使用 C# 嵌套类 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

从嵌套类到包含类的 C# 成员访问 [重复]

C#中的嵌套类

为啥我不能在外部定义嵌套类?

Java中的静态嵌套类,为啥?

编写高质量代码改善C#程序的157个建议——建议109:谨慎使用嵌套类

C# 嵌套类和继承