C#: 传递null给重载方法 - 调用哪个方法?

39

假设我有一个C#方法的两个重载版本:

void Method( TypeA a ) { }
void Method( TypeB b ) { }

我用以下方式调用该方法:

Method( null );

哪个方法的重载被调用了?我该怎么做才能确保调用特定的重载方法?


还有一个需要注意的是重载方法,至少有一个方法使用了params关键字。 - Mark Simpson
6个回答

75

这取决于TypeATypeB

  • 如果只有其中一个适用(例如,从nullTypeB没有转换因为它是值类型但TypeA是引用类型),那么调用将被应用于适用的类型。
  • 否则,它取决于TypeATypeB之间的关系。
    • 如果存在从TypeATypeB的隐式转换但没有从TypeBTypeA的隐式转换,则使用使用TypeA的重载。
    • 如果存在从TypeBTypeA的隐式转换但没有从TypeATypeB的隐式转换,则使用使用TypeB的重载。
    • 否则,调用是二义性的并且无法编译。

有关详细规则,请参见C# 3.0规范的第7.4.3.4节。

以下是不会出现二义性情况的示例。这里TypeB派生自TypeA,意味着从TypeBTypeA存在一个隐式转换,但反之则不然。因此使用使用TypeB的重载:

using System;

class TypeA {}
class TypeB : TypeA {}

class Program
{
    static void Foo(TypeA x)
    {
        Console.WriteLine("Foo(TypeA)");
    }

    static void Foo(TypeB x)
    {
        Console.WriteLine("Foo(TypeB)");
    }

    static void Main()
    {
        Foo(null); // Prints Foo(TypeB)
    }
}

一般来说,即使面对一个本质上不明确的调用,为了确保使用特定的重载,只需进行强制转换:

Foo((TypeA) null);
或者
Foo((TypeB) null);
注意,如果这涉及到声明类的继承(即一个类正在重载其基类声明的方法),那么你就会遇到另一个问题,你需要转换方法的目标而不是参数。

@Jon:为什么在可用隐式转换的情况下,行为会如您所描述?我本来期望相反的结果。这只是定义的问题吗? - Dirk Vollmar
2
@divo: 这绝对是一个设计决策,但背后的原因可能是TypeB方法基本上是TypeA方法的一种专门情况。我的意思是,对于每个有效的参数,默认情况下将匹配TypeA,除非已知对象为TypeB。这种对null的行为确保了这种一致性。 - Mehrdad Afshari
Mehrdad是正确的 - 如果您可以从TypeB转换为TypeA,则TypeB比TypeA更具体,因此将优先选择TypeB。 - Jon Skeet
强制类型转换可以帮助编译器确定要调用哪个版本,非常方便。 - liang

8

Jon Skeet给出了一份全面的答案,但从设计的角度来看,您不应该依赖于编译器规范的边缘情况。如果没有其他选择,在编写代码之前必须查找其含义,那么下一个尝试阅读它的人也将不知道它的含义。这是不可维护的。

重载方法是为了方便而存在的,同名的两个不同重载方法应该执行相同的操作。如果两个方法执行不同的操作,请重命名其中一个或两个都重命名。

通常,重载方法会有具有不同参数数量的变体,并且参数较少的重载方法会提供合理的默认值。

例如:string ToString(string format, System.IFormatProvider provider)具有最多的参数,
string ToString(System.IFormatProvider provider)提供默认格式,和
string ToString()提供默认格式和提供程序。


我不明白你对于重载的观点。确实,重载应该做同样的事情,但可能会以不同的方式完成。这就是为什么你需要重载。参数的数量是无关紧要的,可以相同也可以不同(例如:各种Convert方法)。关于编译器的特定问题:根据下一个人的技能水平,任何东西都可能成为“边角案例”。只要你的设计合理,并且以直观的方式工作,它就能够胜任其工作。这可能需要在设计时仔细考虑,而在类稍后使用时则不需要太多考虑。 - Tor Haugen
我认为他的观点是,重载最典型的用途是对可能包含不同类型或更少参数的数据执行某些函数。事实上,我编写了一些重载,其中具有较少参数的重载实际上在较少的不同部分中具有完全相同的数据(字符串)。 在这种情况下,它们在拆分这些部分并再次提供它们后调用更高级别的重载。 - Jesse Williams

5

Jon Skeet已经回答了默认情况下会选择哪个重载函数,但是如果你想确保调用特定的重载函数,通常最好使用命名参数而不是转换。

如果你有以下代码:

void Method( TypeA a ) { }
void Method( TypeB b ) { }

您可以调用 Method(a: null);Method(b: null);

1

模糊的调用。(编译时错误)。


你好, 我尝试了下面的示例,没有得到编译错误; 你有什么想法为什么会这样? class Program { void test1(ICollection<String> a) { Console.WriteLine("test1-ICollection"); } void test1(IList<String> a) { Console.WriteLine("test1-List"); } static void Main(string[] args) { Program p = new Program(); p.test1(null); Console.ReadKey(); } } - Krishna Kumar
但是当我将其中一个重载更改为接受类型为IComparer<String>的参数时,我得到了编译错误。class Program { void test1(IList a) { Console.WriteLine("test1-List"); } void test1(IComparer a) { Console.WriteLine("test1-IComparer"); } static void Main(string[] args) { Program p = new Program(); p.test1(null); Console.ReadKey(); } } - Krishna Kumar

0
一个简单的解决方案是创建另一个方法,其签名为:
void Method() { }

或者最好更新其中一个方法的签名为:

void Method( TypeB b = null ) { }

然后这样调用:

Method();

在编译时,值 null 是未命名的,因此编译器无法将其与其中一个方法签名匹配。 在运行时,任何可能解析为 null 的变量仍将被分配类型,因此不会引起问题。


-1

只需将其强制转换为您想要的重载值即可

void method(int);
void method(string);
method((string) null){};

这将调用


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