在派生类中重载基方法

Posted

技术标签:

【中文标题】在派生类中重载基方法【英文标题】:Overloading base method in derived class 【发布时间】:2013-05-18 12:37:19 【问题描述】:

所以我在玩 C#,看看它是否与这篇文章中的 C++ 行为匹配:http://herbsutter.com/2013/05/22/gotw-5-solution-overriding-virtual-functions/ 当我遇到这种非常奇怪的行为时:

public class BaseClass

    public virtual void Foo(int i)
    
        Console.WriteLine("Called Foo(int): " + i);
    

    public void Foo(string i)
    
        Console.WriteLine("Called Foo(string): " + i);
    


public class DerivedClass : BaseClass

    public void Foo(double i)
    
        Console.WriteLine("Called Foo(double): " + i);
    


public class OverriddenDerivedClass : BaseClass

    public override void Foo(int i)
    
        base.Foo(i);
    

    public void Foo(double i)
    
        Console.WriteLine("Called Foo(double): " + i);
    


class Program

    static void Main(string[] args)
    
        DerivedClass derived = new DerivedClass();
        OverriddenDerivedClass overridedDerived = new OverriddenDerivedClass();

        int i = 1;
        double d = 2.0;
        string s = "hi";

        derived.Foo(i);
        derived.Foo(d);
        derived.Foo(s);

        overridedDerived.Foo(i);
        overridedDerived.Foo(d);
        overridedDerived.Foo(s);
    

输出

Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi
Called Foo(double): 1
Called Foo(double): 2
Called Foo(string): hi

显然,它有利于隐式转换的 int 比基类中更具体的 Foo(int) 加倍。或者它是否从基类中隐藏了 Foo(int) ?但是然后:为什么 Foo(string) 不隐藏?感觉很不一致......我是否覆盖 Foo(int) 也没关系;结果是一样的。谁能解释这里发生了什么?

(是的,我知道是不好的做法 - Liskov 等等 - 但我仍然不希望 OverriddenDerivedClass 中的 Foo(int) 不被调用?!)

【问题讨论】:

它在那里,向下滚动 "是的,我知道是不好的做法 - Liskov 和所有" 嗯,什么?那么什么时候允许覆盖基本方法呢?无论如何+1,因为我希望同样,好奇的答案。 @DimitarDimitrov 向飞碟祈祷,这样他就可以在神圣的智慧之光中保佑我们。 说到伟大的飞碟:***.com/questions/2744528/… Eric Lippert 回应 Jon Skeet 脑筋急转弯不会出错。 【参考方案1】:

解释它在OverriddenDerivedClass 示例中的工作原理:

在此处查看用于成员查找的 C# 规范:http://msdn.microsoft.com/en-us/library/aa691331%28VS.71%29.aspx

这定义了查找是如何完成的。

特别看这部分:

首先,构造在 T 中声明的所有可访问(第 3.5 节)成员 N 和 T 的基本类型(第 7.3.1 节)的集合。包含覆盖修饰符的声明被排除在集合之外。

在您的情况下,NFoo()。因为Declarations that include an override modifier are excluded from the set,所以override Foo(int i) 被排除在集合之外。

因此,只剩下未被覆盖的Foo(double i),因此它就是被调用的那个。

这就是OverriddenDerivedClass 示例的工作原理,但这不是DerivedClass 示例的解释。

为了解释这一点,请查看规范的这一部分:

接下来,被其他成员隐藏的成员将从集合中移除。

DerivedClass 中的Foo(double i)Foo(int i) 隐藏在基类中,因此将其从集合中移除。

这里的棘手之处在于:

从集合中删除所有与在 S 的基类型中声明的 M 具有相同签名的方法。

你可能会说“但等等!Foo(double i)Foo(int i)有相同的签名,所以它不应该从集合中删除!”。

但是,由于存在从 int 到 double 的隐式转换,它认为具有相同的签名,因此将Foo(int i) 从集合中删除。

【讨论】:

((BaseClass)derived).Foo(i) 好的,谢谢。但实际上:“......并且 T...... 的基本类型(第 7.3.1 节)意味着 BaseClass::Foo(int) 将在范围内。我可以想象的是该集合看起来像:DerivedClass: :Foo(double), BaseClass::Foo(int)。当它接受整数参数时,它知道它可以隐式地将整数转换为双精度,并且 BaseClass::Foo(int) 不会被评估。但这只会是真的当具有 Foo(double) Foo(int) 的普通类具有这样的集合时:NormalClass:Foo(int), NormalClass:Foo(double)。我们可以验证一下吗? @Nebula 抱歉,不太清楚你的意思......但是,基础Foo(int) 在范围内,但是因为它与派生类Foo(double) 具有相同的签名(因为有一个从 int 到 double 的隐式转换)派生类的 Foo(double) 导致基类的 Foo(int) 从集合中删除。 @MatthewWatson 是的,这确实是发生的事情。然而,我不清楚这个决定是如何做出的:派生方法是否优于基类方法?即使必须转换类型(即“强制”)以适合签名?换句话说,它是由编译器做出的决定(查看派生和基数并比较签名)还是编译器首先查看派生列表以查看它是否可以匹配(任何匹配,使用隐式转换等等),如果是这样,那么完全忽略基类? @Nebula 它完全遵循规范概述的步骤(请参阅我发布的完整规范链接)。规范的该部分未定义的唯一重要的事情是关键点,即基类中具有参数类型的方法具有从派生方法中的相应参数类型进行隐式转换的关键点,这些方法被认为具有相同的签名。我在规范中找不到...但是有人链接的 Eric Lippert 的博客解释得更好:blogs.msdn.com/b/ericlippert/archive/2007/09/04/…

以上是关于在派生类中重载基方法的主要内容,如果未能解决你的问题,请参考以下文章

为啥派生类中的重写函数会隐藏基类的其他重载?

为啥派生类中的重写函数会隐藏基类的其他重载?

为啥派生类中的重写函数会隐藏基类的其他重载?

C++,如何在派生类中调用基类的重载提取运算符?

派生类不从基类继承重载方法

C++中派生类重写基类重载函数时需要注意的问题:派生类函数屏蔽基类中同名函数