代码之家  ›  专栏  ›  技术社区  ›  Fredrik Ljung

在继承基类时,C#默认为new而不是override,这有什么原因吗?

  •  3
  • Fredrik Ljung  · 技术社区  · 8 年前

    public class Base
    {
        virtual public string GetString() => "Hello from Base";
    }
    
    public class Child : Base
    {
        public string GetString() => "Hello from Child";
    }
    
    ...
    
    var childAsBase = (Base)new Child();
    Console.WriteLine(childAsBase.GetString());
    
    ...
    
    c:\>dotnet run
    child.cs(5,23): warning CS0114: 'Child.GetString()' hides inherited member 'Base.GetString()'.
    To make the current member override that implementation, add the override keyword. 
    Otherwise add the new keyword. [C:\IPutAllMyProjectsInMyRootFolder.csproj]
    Hello from Base
    

    我可以认为,无论继承的方法是否标记为虚拟,都可以获得相同的行为,但同时,声明为虚拟就是说“覆盖我”,因此默认覆盖对我来说似乎是合理的。

    我脑海中浮现的另一个原因是使用虚拟函数表的成本,但这似乎是一个可怕的原因,因为作为一名编码器,我希望代码做的事情应该比节省cpu周期更重要。但也许回到语言发明的时候,情况并非如此?

    1 回复  |  直到 8 年前
        1
  •  9
  •   Eric Lippert    8 年前

    当涉及类型层次结构的C#语言设计决策对您来说似乎不常见时,一个好方法是问自己这样一个问题:“如果有人不告诉我就更改了我的基类,会发生什么?”C#经过精心设计,以降低 脆性基类故障 ,这是一个。

    让我们首先考虑阴影方法具有 override

    这向编译器表明: 派生类作者和基类作者正在合作 . 基类作者创建了一个可重写的方法,它是一个 超危险的事情 您不能编写测试该方法所有可能行为的测试用例! 方法的可重写性 必须在中设计

    如果我们看到 然后,我们知道基类和派生类的作者都对这个危险扩展点的正确性和安全性负责,并已成功地相互沟通以达成协议。

    接下来,让我们考虑阴影方法具有 new 关键字。同样,我们知道派生类作者 检查基类 确定阴影方法(无论是否为虚拟方法)不满足派生类使用者的需要 ,并且 做出了危险的决定,让两种方法具有相同的签名。

    推翻 也没有 . 我们没有证据表明派生类的作者知道基类中的方法 . 事实上,我们有相反的证据;如果他们知道虚拟基类方法,他们就会重写它 匹配虚拟方法的契约

    这种情况怎么会出现?我只想到两种方法。

    首先,派生类作者 没有充分研究它们的基类,并且不知道它们刚刚隐藏的方法的存在 极坏的

    其次,派生类是 . 现在,派生类作者并非不知道基类 正如原著所写

    同样,我们必须警告无知的开发人员,发生了一些他们需要做出重要决定的事情:如果可能的话重写,或者确认隐藏,或者重命名或删除派生类方法。

    也没有 推翻 ?"

    好吧,假设您是编译器开发人员。以下是当编译器面临缺少的阴影方法时的选择 推翻

    • 什么都不做;不要给出警告或错误,选择一种行为。如果代码由于脆弱的基类失败而中断,那就太糟糕了。您应该更仔细地研究基类。显然,我们可以做得更好。

    • 让它成为一个错误。现在,基类作者可以 打破你的身材 通过更改基类的成员。这不是一个可怕的想法,但我们现在必须权衡 不需要的构建中断 意外忽略警告并引入错误 .

    • 将其设置为警告,如果基方法可重写,则将其设置为重写;如果基方法不可重写,则将其设置为阴影。这不仅不一致,而且 . 你看到了吗? 如果基类作者将其方法从非虚拟更改为虚拟,或者反之亦然,该怎么办? 这会导致意外阴影方法 从覆盖到阴影,或反之亦然。

    但我们暂时把它放在一边。如果可能,自动覆盖的其他后果是什么?回想起 .

    自动改变的行为 极度危险 与改变行为的危险相比 .

    • 将其设置为警告,并默认为阴影,而不是覆盖。这种选择通常更安全,它避免了第二种脆性基底破坏,避免了构建中断, 具有基类接收器的方法调用方获得了其测试用例所期望的行为 ,并且具有派生类接收器的方法的调用方获得该行为 他们 预料

    所有设计选择都是仔细权衡许多相互不兼容的设计目标的结果。C#的设计者特别关注在版本化软件组件上工作的大型团队,其中基类可能会以意外的方式更改,团队之间可能无法很好地沟通这些更改。

    我脑海中浮现的另一个原因是使用虚拟函数表的成本,但这似乎是一个可怕的原因,因为作为一名编码器,我希望代码做的事情应该比节省cpu周期更重要。但也许回到语言发明的时候,情况并非如此?

    虚拟方法引入成本;明显的成本是运行时额外的表跳转,以及实现跳转所需的代码。还有一些不太明显的成本,例如:抖动无法将虚拟调用内联到非密封方法,等等。

    昂贵的 ,使其选择加入可以降低成本并提高安全性。坦白说,我希望 也是默认值。