【问题标题】:Why can't my public class extend an internal class?为什么我的公共类不能扩展内部类?
【发布时间】:2010-09-02 12:15:37
【问题描述】:

我真的不明白。

如果基类是抽象的并且仅用于为程序集中定义的公共子类提供通用功能,为什么不应该将其声明为内部的?

我不希望抽象类对程序集外的代码可见。我不想让外部代码知道它。

【问题讨论】:

    标签: c# inheritance internal


    【解决方案1】:

    更新:这个问题是the subject of my blog on November 13th of 2012。有关此问题的更多想法,请参见它。谢谢你的好问题!


    你是对的;它不必是这样的。其他 OO 语言允许“私有继承”,即 D 从 B 继承的事实只能被能够看到 B 的代码利用。

    这是最初 C# 设计师的设计决定。不幸的是,我现在不在办公桌前——我将在长周末休假几天——所以我面前没有 1999 年的语言设计笔记。如果我回来后想到它,我会浏览它们,看看是否有这个决定的理由。

    我个人的看法是,应该用继承来表示“是一种”的关系;也就是说,继承应该表示用语言建模的域的语义。我尽量避免将继承用作代码共享机制的情况。正如其他人所提到的,如果您想要表示的是“这个类与其他类共享实现机制”,那么最好选择组合而不是继承。

    【讨论】:

    • “更喜欢封装而不是继承” - 你能举个例子吗?我不确定我是否完全理解您在这里的意思,或者您将如何使用封装来提供共享实现代码。
    • @Erik:我打算输入“首选组合”,但不小心输入了“首选封装”。一个相当大的错字!
    • 我明白你的意思,尽管对于比我更好地掌握语言的人来说,在建筑上抚摸下巴是一种奢侈! (我的意思是最好的方式,如果误读为讽刺或攻击性。)
    • @Eric - 啊,好吧。很高兴我没有完全遗漏任何东西,哈哈 =) 感谢您清理它。
    • @James:这些笔记的形式不适合公众消费。我们可以花时间调试编译器并添加功能,也可以花时间清理数千页的设计说明,其中大部分是关于十年前删减的功能。我们的绝大多数客户都希望我们将预算用于之前的活动。
    【解决方案2】:

    通过从一个类继承,您可以通过您的子类公开基类的功能。

    由于子类比其父类具有更高的可见性,因此您将暴露本应受到保护的成员。

    您不能通过实现具有更高可见性的子类来违反父类的保护级别。

    如果基类确实要供公共子类使用,那么您也需要将父类设为公共。

    另一种选择是将“父”保持在内部,使其非抽象,并使用它来组合您的子类,并使用接口强制类实现功能:

    public interface ISomething
    {
        void HelloWorld();
    }
    
    internal class OldParent : ISomething
    {
        public void HelloWorld(){ Console.WriteLine("Hello World!"); }
    }
    
    public class OldChild : ISomething
    {
        OldParent _oldParent = new OldParent();
    
        public void HelloWorld() { _oldParent.HelloWorld(); }
    }
    

    【讨论】:

    • 谢谢。但是为什么不应该将内部类的成员视为内部成员,除非通过公共子类公开?我认为这是有道理的。
    • 感谢您详细的回答和使用作文的建议。我是否认为我的问题的答案更多的是“因为 C# 不是以这种方式创建的”而不是“因为这样做是一件愚蠢的事情”?
    • 这是一个约定俗成的问题。如果您希望通过继承更改类的范围,您可以简单地不更改规则以使某些内容更易于访问。内部应该专门用于使程序集中的所有类都可以访问某些东西。通过使子类可用于其他程序集,将范围更改为公共违反了这一点。例如,内部类的一个很好的用途是保护核心框架问题。如果你坚持你的内部类应该可以在程序集之外访问,你可能不应该使用 internal 修饰符。
    • 我更喜欢接口方法,但我认为 David 特别希望将此类功能保留在“公共位置”,而不必在每个派生类中重新实现相同的代码。
    • 我不认为这是“因为 C# 不是这样创建的”的问题。内部不是限制,而是功能。它类似于在 Java 中创建一个没有访问修饰符的类,将其限制在 JAR 中。它有一个非常特定的目的,只是一个不经常使用(或经常使用,如果你愿意的话)的目的。
    【解决方案3】:

    我认为您可以做的最接近的事情是通过将其构造函数设为内部来防止其他程序集创建抽象类,引用MSDN:

    内部构造函数防止抽象类被用作与抽象类不在同一程序集中的类型的基类。

    然后您可以尝试将EditorBrowsableAttribute 添加到类中以尝试将其隐藏在 IntelliSense 中(不过,老实说,我使用它的结果好坏参半)或将基类放在嵌套命名空间中,例如 @ 987654323@ 将其与您的其他课程分开。

    【讨论】:

    • 在我看来,这才是真正的答案。
    【解决方案4】:

    我认为您在这里混淆了关注点,实际上应该归咎于 C#(以及之前的 Java)。

    继承应该作为一种分类机制,而它通常用于代码重用。

    对于代码重用,大家都知道组合优于继承。 C# 的问题在于它为我们提供了一种简单的继承方式:

    class MyClass : MyReusedClass { }
    

    但是为了作曲,我们需要自己来做:

    class MyClass {
      MyReusedClass _reused;
      // need to expose all the methods from MyReusedClass and delegate to _reused
    }
    

    缺少的是像trait (pdf) 这样的构造,它将使组合达到与继承相同的可用性级别。

    有关于traits in C# (pdf) 的研究,它看起来像这样:

    class MyClass {
      uses { MyTrait; }
    }
    

    虽然我希望看到another model(Perl 6 角色的那个)。

    更新:

    作为旁注,Oxygene 语言有a feature,它允许您将接口的所有成员委托给实现该接口的成员属性:

    type
      MyClass = class(IReusable)
      private
        property Reused : IReusable := new MyReusedClass(); readonly;
          implements public IReusable;
      end;
    

    在这里,IReusable 的所有接口成员都将通过MyClass 公开,并且它们都将委托给Reused 属性。不过,有一些 problems 采用这种方法。

    另一个更新:

    我已经开始在 C# 中实现这个自动组合概念:看看NRoles。

    【讨论】:

    • 我完全理解你的观点,但我觉得你的“组合胜过继承”的断言可以扩展。你能说点别的来解释你为什么这么认为吗?谢谢。
    • @David:我说过组合在代码重用方面胜过继承。您不应该仅仅为了重用代码而遵守继承层次结构。层次结构说明了你是谁,这是一种非常牢固的关系(在单继承语言中非常珍贵)。对于代码重用,你真的很想说明你所做的事情,而组合和委托允许你这样做。接口也擅长于此,但您仍然需要实现其成员或委托给这样做的人。
    • “继承”的概念实际上包括两个正交的概念:具有默认行为的代码重用和可替换性。当两者都需要时,继承是合适的;当只需要代码重用时,组合会更好。真正酷的是对接口组合的通用支持,因此如果 II 是一个接口,则对象可以根据该类型的字段来实现 II。如果没有对接口的通用约束,这显然是行不通的,但如果微软可以在某个时候添加它会非常方便。
    • @supercat:看看我对具有此功能的语言的第一次更新。并查看我的第二次更新以了解更好的功能:-)
    • @Jordão:我对使用后编译器的想法并不完全满意,尽管我确信对它们感到满意的人会发现它们很有用。我想如果主要编译器的制造商拒绝提供有用的接口,那么就必须做必须做的事情。尽管如此,我认为如果 C#/vb.net 允许一个类“内部”继承另一个类但不能被认为可以替代外部人员,那将是有帮助的;行为应该就好像“内部继承”类被复制了,但希望没有实际复制代码。
    【解决方案5】:

    我认为这违反了Liskov Substitution Principle。

    在这种情况下,我使用了内部类并且更喜欢组合而不是继承。您的设计是否有任何内容禁止在内部类中包含所有此类功能,然后让您的公共类包含此内部类的实例?

    【讨论】:

    • 如何违反替代原则?我不明白你在说什么。
    • @Eric 我猜我倒退了。如果您的派生类删除了基类提供的功能,则您违反了规则,因此您将无法用派生类替换您的基类。当我第一次阅读这个问题时,我得出了结论。这只是当时的一个想法——我的回答的主要部分仍然是 IMO——只需使用组合而不是继承。谢谢指正。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    相关资源
    最近更新 更多