哪个C#方法重载被选择?

28
为什么在重载方法匹配时,会调用泛型方法?
public static void method1(object obj)
{
    Console.WriteLine("Object");
}

public static void method1<T>(T t)
{
    Console.WriteLine("Type T");
}

public static void Main(String args[])
{
    method1("xyz"); //Will print "Type T";
}

这里不应该有任何冲突,对吧?

类似于这个问题 - adelphus
4个回答

47

通过选择最具体的重载来解决重载问题。在本例中,method1<string>(string)method1(object)更具体,因此选择了这个重载。

有关详细信息,请参见C#规范第7.4.2节

如果要选择特定的重载版本,可以将参数显式转换为所需的类型。以下示例将调用method1(object)重载而不是通用的重载:

method1((object)"xyz"); 

有些情况下编译器无法确定选择哪个重载函数,例如:

void method2(string x, object y);
void method2(object x, string y);

method2("xyz", "abc");

在这种情况下,编译器不知道选择哪个重载,因为两个重载都不比另一个优越(它不知道将哪个字符串隐式地转换为对象)。因此,它会发出编译器错误。


有最新的规范链接吗? - derHugo
这里:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/readme 看起来章节号已经改变,我还没有找到他们讨论重载决策的地方。 - Erik
谢谢!我已经找到了7.6 签名和重载,但我还没有找到哪个重载会被优先选择的规范 - 我的意思是你基本上已经说了,但我怎么知道什么是“更具体”的,比如在接口和泛型中等。 - derHugo

12
C#会始终选择最具体的可用方法。
编译时,C#会查找与调用方法最匹配的方法。如果有多个方法都可以匹配,C#将选择最具体的方法。
method1("xyz");

它将查找所有指定名称的方法,然后尝试匹配参数。编译器会选择最具体的方法,在这种情况下,它更喜欢

method1(string s)

结束

method1<T>(T t) with T = string

最后,
method1(object o)

请注意 @Erik 的杰出回答,其中有一个编译器无法决定的示例。

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - juharr
@juharr 您说得对,让我稍微修改一下我的回答。 - Vlad274
你仍然需要移除 method1(string s) 部分。它更倾向于使用 method<T>(T t) 而非 method1(object o) - juharr
@juharr 您在这种情况下是正确的,但我想要更通用的解决方案。 - Vlad274

1
因为您已经将T作为参数传递,所以您不需要键入method1<string>("xyz");,您可以直接输入method1("xyz");,.Net已经知道它是一个字符串。如果您有 method1,则情况会有所不同。
另外,由于method1(object obj)不接受字符串作为参数,它将首先使用可以推断出T的通用函数。如果您将method1(object obj)更改为method1(string obj),它将首选该函数,然后才是通用函数。

其实,实现泛型类型推断的是C#编译器;p它与.NET无关。 - Matías Fidemraizer

0

方法重载的工作原理

为了找到调用方法的匹配签名,编译器会从下往上在类型层次结构中搜索,同时也会在虚拟表中进行搜索:

  • 首先在类层次结构中搜索,
  • 然后在接口层次结构中搜索。

因为类优先于接口。

事实上,在成为接口类型之前,对象首先是一个类的类型。

非泛型签名优先于泛型签名,就像现实和事实优先于抽象一样,除非使用泛型参数允许调用更专业类型的实例。

将理论应用于问题

这个调用:

method1("xyz");

与之完美匹配:

void method1<T>(T t) { }

匹配前:

void method1(object obj)

因为字符串是一个专门的对象,可以用作通用参数以更准确地使用。

另一方面,如果您编写:

void method1(string obj) { }

void method1<T>(T t) { }

第一种方法被称为。

案例研究

var instance = new List<string>();

MyMethod(instance);
MyMethod((IEnumerable<string>) instance);
MyMethod<string>(instance);
MyMethod((object)instance);

void MyMethod<T>(List<T> instance) { }

void MyMethod<T>(IEnumerable<T> list) { }

void MyMethod<T>(T instance) { }

void MyMethod(object instance) { }
  • 第一次调用调用了第一个方法,因为实例的类型是List(类型匹配)。

  • 第二次调用调用了第二个方法,因为进行了侧向转换(实现)。

  • 第三次调用调用了第三个方法,因为指定了要操作的泛型参数(模板化)。

  • 第四次调用调用了第四个方法,因为进行了向下转换(多态性)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接