重载是许多编程语言支持的特性。所谓重载,就是指可以定义多个名称相同但参数(个数、类型和顺序)不同的方法(函数)。先来看一个例子:
void Main()
{
char cvalue = 'a';
male m = new male();
m.write(cvalue);
}
class human
{
public void write(char value)
{
Console.WriteLine("char:" + value);
}
}
class male : human
{
public void write(int value)
{
Console.WriteLine("int:" + value);
}
}
这个例子中,父类human中有个一个参数类型为char的函数write,子类(derived class)male中提供了参数类型为int的重载函数,在Main方法中实例化了一个子类male的对象m,对象m调用write方法,并传递char类型的参数。最终执行的结果是int:97
,而不是char:a
。为何在函数调用的时候,没有找到父类中参数类型完全匹配的函数,而是进行了类型转换呢?
有人说这是因为 .NET的类型推断(type inference)不够智能。其实,这里并不涉及类型推断,因为类型推断主要是针对隐式类型和泛型的。
这里是由重载决策机制决定的。重载决策是一种绑定时机制,用于在给定参数列表和一组候选函数成员的情况下选择要调用的最佳函数成员。c#中支持重载的有以下几种情况:
虽然上述四种情况都有自己独有地定义重载函数和参数列表的方式,但是重载决策选择最佳函数调用地过程是一致的。
根据C# Language Specification中成员查找(Member lookup)的描述,方法调用的候选集不包括标记为override的方法。根据方法调用(Method invocations)的描述,子类中只要有一个方法适用,则父类的函数不是候选函数。这也就解释了开篇的例子中为何没有选择父类中参数类型完全匹配的函数。
当满足以下所有条件时,函数成员被称为与参数列表A的适用函数成员:
接下来用几个例子来说明函数是否适用
void Main()
{
int i = 10; uint ui = 34U;var vi=5;dynamic di=5;
M1(in i); // M1(in int)合适
M1(in ui); // 没有精准匹配, 因此M1(in int)不适用
M1(i); // M1(int) 和 M1(in int)都适用
M1(ui); // uint到int不存在隐式转换,所以M1(int) 不适用
M1(in vi); //恒等转换(identity conversion),M1(in int)适用
M2(ui); //uint隐式转换为long,因此M2(long)适用;同时传参没带in修饰符,允许装箱为object的隐式转换,M2(in object)适用
M2(di); //不适用
M2(in di) //恒等转换(identity conversion),M2(in object)适用
M2(in vi) //恒等转换(identity conversion),M2(in object)适用
M3(ui) //ref和out传递模式不支持忽略ref和out修饰符匹配,因此M3(ref object)不适用
}
public static void M1(int p1) { Console.WriteLine("M1 int:"+p1); }
public static void M1(in int p1) { Console.WriteLine("M1 in int:"+p1); }
public static void M2(long p1) { Console.WriteLine("M2 long:"+p1); }
public static void M2(in object p1) { Console.WriteLine("M2 in object"+p1); }
public static void M3(ref object p1) { Console.WriteLine("M3 ref object" + p1); }
假设调用函数时传递的参数为{E₁, E₂, ..., Eᵥ},有两个适用的函数Mᵥ(P₁, P₂, ..., Pᵥ)
和Mₓ(Q₁, Q₂, ..., Qᵥ)
,满足以下条件时则认为Mᵥ
是更合适的函数:
Eᵥ
到Qᵥ
的隐式转换没有比Eᵥ
到Pᵥ
的隐式转换更好Eᵥ
到Pᵥ
的转换比Eᵥ
到Qᵥ
的转换好。如果按照上述规则比较,函数Mᵥ(P₁, P₂, ..., Pᵥ)
和Mₓ(Q₁, Q₂, ..., Qᵥ)
是等价的(例如每个Pᵢ
和Qᵢ
是恒等转换关系),则继续根据以下规则判断更好的函数:
Mᵢ
是非泛型方法,而Mₑ
是泛型方法,则认为Mᵢ
更合适Mᵢ
是普通方法,而Mᵢ
是扩展方法,则认为Mᵢ
更合适Mᵢ
和Mᵢ
都是扩展方法,并且Mᵢ
的参数更少,则认为Mᵢ
更合适Mᵢ
的参数中有比Mᵢ
的对应参数更具体地类型,则认为Mᵢ
更合适当两个重载方法中对应的形参仅在形参传递模式上不同,并且两个函数形参中的一个具有值传递模式,例如
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
前边适用的函数成员部分提到,调用M(10)
方法时,两个重载方法都适用。这种情况下,值传递模式是更好的参数传递模式。
ref和out传递模式不支持忽略ref和out修饰符匹配,必须精准匹配