重载解析和虚拟方法

发布于 2024-09-17 23:32:34 字数 667 浏览 4 评论 0 原文

考虑以下代码(它有点长,但希望您能理解):

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}

如果您认为该程序的输出是“Foo(B)”,那么您会和我一样:完全错误!事实上,它输出“Foo(A)”

如果我从 C 类中删除虚拟方法,那么它会按预期工作:“Foo(B)”是输出。

B 是更多派生类时,为什么编译器会选择采用 A 的版本?

Consider the following code (it's a little long, but hopefully you can follow):

class A
{
}

class B : A
{
}

class C
{
    public virtual void Foo(B b)
    {
        Console.WriteLine("base.Foo(B)");
    }
}

class D: C
{
    public override void Foo(B b)
    {
        Console.WriteLine("Foo(B)");
    }

    public void Foo(A a)
    {
        Console.WriteLine("Foo(A)");
    }
}

class Program
{
    public static void Main()
    {
        B b = new B();
        D d = new D ();
        d.Foo(b);
    }
}

If you think the output of this program is "Foo(B)" then you'd be in the same boat as me: completely wrong! In fact, it outputs "Foo(A)"

If I remove the virtual method from the C class, then it works as expected: "Foo(B)" is the output.

Why does the compiler choose the version that takes a A when B is the more-derived class?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最丧也最甜 2024-09-24 23:32:35

为什么编译器选择采用 A 的版本,而 B 是更派生的类?

正如其他人所指出的,编译器这样做是因为这是语言规范所说的。

这可能是一个令人不满意的答案。自然的后续问题是“以这种方式指定语言的决定背后的设计原则是什么?”

这是 StackOverflow 上和我的邮箱中常见的问题。简短的回答是“这种设计减轻了脆弱基类系列的错误。”

有关该功能的说明以及为何如此设计,请参阅我关于该主题的文章:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/future-writing-changes-part- Three

了解更多有关各种语言如何处理脆弱基类问题的文章请参阅我有关该主题的文章存档:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

这是我上周对同一问题的回答,看起来非常像这个。

为什么在基类中声明签名被忽略?

这里还有三个更相关或重复的问题:

C# 重载解析?

方法重载解析和 Jon Skeet 的脑筋急转弯

为什么这有效?方法重载+方法重写+多态

Why does the compiler choose the version that takes a A when B is the more-derived class?

As others have noted, the compiler does so because that's what the language specification says to do.

This might be an unsatisfying answer. A natural follow-up would be "what design principles underly the decision to specify the language that way?"

That is a frequently asked question, both on StackOverflow and in my mailbox. The brief answer is "this design mitigates the Brittle Base Class family of bugs."

For a description of the feature and why it is designed the way it is, see my article on the subject:

https://learn.microsoft.com/en-us/archive/blogs/ericlippert/future-breaking-changes-part-three

For more articles on the subject of how various languages deal with the Brittle Base Class problem see my archive of articles on the subject:

http://blogs.msdn.com/b/ericlippert/archive/tags/brittle+base+classes/

Here's my answer to the same question from last week, which looks remarkably like this one.

Why are signatures declared in the base class ignored?

And here are three more relevant or duplicated questions:

C# overloading resolution?

Method overloads resolution and Jon Skeet's Brain Teasers

Why does this work? Method overloading + method overriding + polymorphism

長街聽風 2024-09-24 23:32:35

我认为这是因为在非虚拟方法的情况下,使用调用该方法的变量的编译时类型。

您有 Foo 方法,它是非虚拟的,因此该方法被调用。

此链接有很好的解释http://msdn。 microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

I think it is because in case of a non-virtual method the compile time type of the variable on which the method is invoked is used.

You have the Foo method which is non-virtual and hence that method is called.

This link has very good explanation http://msdn.microsoft.com/en-us/library/aa645767%28VS.71%29.aspx

前事休说 2024-09-24 23:32:35

因此,它应该如何工作 根据规范< /a> (在编译时,假设我正确导航了文档):

编译器根据方法名称和参数,从类型 D 及其基本类型中识别匹配方法的列表列表。这意味着任何名为 Foo 且采用从 B 隐式转换为某一类型的参数的方法都是有效的候选方法。这将产生以下列表:

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

从该列表中,任何包含 override 修饰符的声明都被排除。这意味着列表现在包含以下方法:

C.Foo(B) (public virtual)
D.Foo(A) (public)

此时我们有了匹配候选者的列表,编译器现在决定调用什么。在文档 7.5.5.1 方法调用中,我们找到以下文字:

如果 N 适用于 A(部分7.4.2.1),然后从集合中删除 T 基类型中声明的所有方法。

这本质上意味着,如果在 D 中声明了适用的方法,则基类中的任何方法都将从列表中删除。此时我们有一个胜利者:

D.Foo(A) (public)

So, here is how it should work according to the specification (at compile time, and given that I navigated the documents correctly):

The compiler identifies a list of matching methods from the type D and its base types, based on the method name and the argument list. This means that any method named Foo, taking one parameter of a type to which there is an implicit conversion from B are valid candidates. That would produce the following list:

C.Foo(B) (public virtual)
D.Foo(B) (public override)
D.Foo(A) (public)

From this list, any declarations that include an override modifier are excluded. That means that the list now contains the following methods:

C.Foo(B) (public virtual)
D.Foo(A) (public)

At this point we have the list of matching candidates, and the compiler is now to decide what to call. In the document 7.5.5.1 Method invocations, we find the following text:

If N is applicable with respect to A (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set.

This essentially means that if there is an applicable method declared in D, any methods from base classes will be removed from the list. At this point we have a winner:

D.Foo(A) (public)
妞丶爷亲个 2024-09-24 23:32:35

我认为,当实现另一个类时,它会尽可能地向上查找,以获得方法的可靠实现。由于没有调用任何方法,因此它使用基类。
<代码>

public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}


这是我的猜测,我不是 .Net 的专业人士

I think that when implementing another class it looks as far up the tree to get an solid implementation of a method. As there is no method being called it is using the base class.

public void Foo(A a){
    Console.WriteLine("Foo(A)" + a.GetType().Name);
    Console.WriteLine("Foo(A)" +a.GetType().BaseType );
}

thats a guess i am no pro at .Net

天赋异禀 2024-09-24 23:32:34

答案就在 C# 规范中 第 7.3 节第 7.5.5.1 节

我分解了用于选择调用方法的步骤。

  • 首先,在 T (T=class D) 中声明的名为 N (N=Foo) 的所有可访问成员以及 T (T=class D) 的基类型>class C) 被构造。 包含覆盖修饰符的声明将从集合中排除D.Foo(B) 被排除

    S = { C.Foo(B) ; D.Foo(A) }
    
  • 构造方法调用的候选方法集。从通过之前的成员查找找到的与 M 关联的方法集开始,该集被缩减为适用于参数列表 AL (AL=B) 的方法。集合缩减包括将以下规则应用于集合中的每个方法 TN,其中 T (T=class D) 是方法 N (N=Foo) 所在的类型>) 声明:

    • 如果 N 不适用于 AL (第 7.4.2.1 节),然后从集合中删除 N。

      • C.Foo(B) 适用于 AL
      • D.Foo(A) 适用于 AL

        S = { C.Foo(B) ; D.Foo(A) }
        
    • 如果 N 适用于 AL(第 7.4.2.1 节),则从集合中删除 T 基类型中声明的所有方法C.Foo(B) 已从集合中删除

      <前><代码> S = { D.Foo(A) }

最后获胜者是 D.Foo(A)


如果从 C 中删除抽象方法

如果从 C 中删除抽象方法,则初始集合为 S = { D.Foo(B) ; D.Foo(A) }重载解析规则必须用于选择该集合中最好的函数成员

在本例中,获胜者是 D.Foo(B)

The answer is in the C# specification section 7.3 and section 7.5.5.1

I broke down the steps used for choosing the method to invoke.

  • First, the set of all accessible members named N (N=Foo) declared in T (T=class D) and the base types of T (class C) is constructed. Declarations that include an override modifier are excluded from the set (D.Foo(B) is exclude)

    S = { C.Foo(B) ; D.Foo(A) }
    
  • The set of candidate methods for the method invocation is constructed. Starting with the set of methods associated with M, which were found by the previous member lookup, the set is reduced to those methods that are applicable with respect to the argument list AL (AL=B). The set reduction consists of applying the following rules to each method T.N in the set, where T (T=class D) is the type in which the method N (N=Foo) is declared:

    • If N is not applicable with respect to AL (Section 7.4.2.1), then N is removed from the set.

      • C.Foo(B) is applicable with respect to AL
      • D.Foo(A) is applicable with respect to AL

        S = { C.Foo(B) ; D.Foo(A) }
        
    • If N is applicable with respect to AL (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set. C.Foo(B) is removed from the set

          S = { D.Foo(A) }
      

At the end the winner is D.Foo(A).


If the abstract method is removed from C

If the abstract method is removed from C, the initial set is S = { D.Foo(B) ; D.Foo(A) } and the overload resolution rule must be used to select the best function member in that set.

In this case the winner is D.Foo(B).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文