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

本文关键字:泛型方法 为什么 选择 存在 | 更新日期: 2023-09-27 18:00:31

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

Foo<T> called
Process is terminated due to StackOverflowException.

所以,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) 方法将始终是您的情况下最具体的。

重载解析是一种用于选择最佳给定参数列表和一组候选函数成员。重载分辨率选择函数要在 C# 中的以下不同上下文中调用的成员:

    调用
  • 调用表达式中命名的方法(部分7.5.5(. 调用对象创建表达式中指定的实例构造函数(第 7.5.10.1 节(。
  • 通过元素访问调用索引器访问器(第 7.5.6 节(。调用表达式中引用的预定义或用户定义的运算符(第 7.2.3 节和第 7.2.4 节(。

这些上下文中的每一个都定义了候选函数成员集及其自身的参数列表独特的方式,如上面列出的部分所述。为例如,方法调用的候选项集不包括标记为覆盖的方法(第 7.3 节(和基库中的方法如果派生类中的任何方法都是适用(第 7.5.5.1 节(。

MSDN 过载解析

我加粗了我认为与您的问题有关的案文。

这是另一个关于堆栈溢出的问题,可能会有所帮助。它一般讨论方法解析。不涉及重写的方法,但有助于填充一些我没有涉及的过程。

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

Child定义了void Foo<T>(T baz)但不定义void Foo(Baz baz)因此选择了void Foo<T>(T baz)

一般来说,这是有道理的;在实际代码中,如果Foo<T>(T baz)在通过Baz时没有做与Foo(Baz baz)在基中所做的非常相似的工作,那么你的设计是令人困惑的,你应该选择一个新名称。

你也可以使用 public new void Foo(Baz baz)public new virtual void Foo(Baz baz) 来强制在 Child 中定义覆盖(尽管这里需要在层次结构中有一个中间步骤,以便抽象方法有一个实现(,它可以调用base.Foo(baz)(调用基本实现(和/或Foo<Baz>(baz)(调用通用版本(', 但最好避免这种伎俩。

也许当你实现这样的东西时它的行为就像你实现这样的东西一样

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

null 调用它将使用int?

添加这个

    void myMethod(short? i) { }

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

也许有一个内部订单/优先级正在完成?

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

这是通用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