为什么在存在非泛型时选择泛型方法?

Why is a generic method chosen when a non-generic exists?

下面的程序产生这个输出:

Foo<T> called

Process is terminated due to WhosebugException.

因此,Foo(baz) 调用泛型 Foo<T>,但 Bar(baz) 递归并且 调用 Bar<T>

我正在使用 C# 5.0 和 Microsoft .NET。当非泛型方法是 override.

时,编译器似乎选择泛型方法而不是递归

我在哪里可以找到这个规则的解释?(我猜编译器会在这两种情况下选择递归。)

完整的程序如下:

using System;

namespace ConsoleApplication1 {
    class Baz { }

    abstract class Parent {
        public abstract void Foo(Baz baz);
    }

    class Child : Parent {
        void Bar<T>(T baz) {
            Console.WriteLine("Bar<T> called");
        }

        public void Bar(Baz baz) {
            Bar(baz);
        }

        void Foo<T>(T baz) {
            Console.WriteLine("Foo<T> called");
        }

        public override void Foo(Baz baz) {
            Foo(baz);
        }
    }

    class Program {
        static void Main(string[] args) {
            var child = new Child();
            child.Foo(null);
            child.Bar(null);
            Console.ReadLine();
        }
    }
}

根据 MSDN 文档,优先考虑未被覆盖的方法签名。由于 Foo 的非泛型版本被覆盖,它会立即进入选择方法优先级的底部。一般来说,下一步是选择最具体的方法并执行它。对于 Bar 方法,Bar(Baz baz) 方法在您的情况下始终是最具体的。

Overload resolution is a compile-time mechanism for selecting the best function member to invoke given an argument list and a set of candidate function members. Overload resolution selects the function member to invoke in the following distinct contexts within C#:

  • Invocation of a method named in an invocation-expression (Section 7.5.5). Invocation of an instance constructor named in an object-creation-expression (Section 7.5.10.1).
  • Invocation of an indexer accessor through an element-access (Section 7.5.6). Invocation of a predefined or user-defined operator referenced in an expression (Section 7.2.3 and Section 7.2.4).

Each of these contexts defines the set of candidate function members and the list of arguments in its own unique way, as described in detail in the sections listed above. For example, the set of candidates for a method invocation does not include methods marked override (Section 7.3), and methods in a base class are not candidates if any method in a derived class is applicable (Section 7.5.5.1).

MSDN Overload Resolution

我将我认为与您的问题相关的文字加粗了。

Stack Overflow 上的另一个问题可能会有所帮助。它一般讨论方法解析。不涉及覆盖的方法,但有助于填充一些我没有涉及的过程。

当你实现这样的东西时,它可能会表现得像

    void myMethod(long? l) { }
    void myMethod(int? i) { }

null 调用它会使用 int?

添加这个

    void myMethod(short? i) { }

并且仍然用null调用它,代码将切换为short?

也许有一个内部 order/priority 正在完成?

现在用你的代码,我给出这个只是为了说明让编译器决定和程序员决定(显式调用)之间的区别

这是通用的Baz

.method private hidebysig 
    instance void Bar<T> (
        !!T baz
    ) cil managed 
{
    // Method begins at RVA 0x2060
    // Code size 13 (0xd)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldstr "Bar<T> called"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: ret
} // end of method Child::Bar

你的实现

    public void Bar(Baz baz) {
        Bar(baz);
    }

给这个

.method public hidebysig 
    instance void Bar (
        class ConsoleApplication1.Baz baz
    ) cil managed 
{
    // Method begins at RVA 0x206e
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: call instance void ConsoleApplication1.Child::Bar(class ConsoleApplication1.Baz)
    IL_0008: nop
    IL_0009: ret
} // end of method Child::Bar

这个

    public void Bar(Baz baz)
    {
        Bar<Baz>(baz);
    }

给这个

.method public hidebysig 
    instance void Bar (
        class ConsoleApplication1.Baz baz
    ) cil managed 
{
    // Method begins at RVA 0x206e
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: call instance void ConsoleApplication1.Child::Bar<class ConsoleApplication1.Baz>(!!0)
    IL_0008: nop
    IL_0009: ret
} // end of method Child::Bar

重载解析搜索继承链,在每个点寻找定义的方法。

Child 定义了 void Foo<T>(T baz) 但没有定义 void Foo(Baz baz) 所以选择了 void Foo<T>(T baz)

通常这是有道理的;在实际代码中,如果 Foo<T>(T baz) 没有做与基础中 Foo(Baz baz) 在传递 Baz 时所做的非常相似的工作,那么你的设计很混乱,你应该选择一个新名称。

您可以使用 public new void Foo(Baz baz)public new virtual void Foo(Baz baz) 强制在 Child 中定义覆盖(尽管这里需要一个中间步骤)在层次结构中,以便抽象方法有一个实现)可以调用 base.Foo(baz)(调用基本实现)and/or Foo<Baz>(baz)(调用通用版本)`,但是这样最好避免技巧。