这个问题又出现在我参与分享的一个C#开发人员邮件列表中。因为还有这一主题的合理数量混乱,我想我会发布规则以及这些规则背后的原因。
这里发布的所有东西都来自<<C#语言规范>>(第四版) 7.5.2节(类型引用) 和7.5.3节(重载解析).
考虑这两个Log的重载方法:
static void Log<K,V>(K key, V value)static void Log<K,V>(string key, IDictionary<K, V> values)
假如你调用下列代码:
class FooDict : Dictionary<string, int>
{
}
// create custom dictionary
FooDict fooDict = new FooDict();
// should *not* use the IDictionary<K,V>
Log("FooDict", fooDict);
// try a regular dictionary value
Dictionary<string, int> dict = fooDict;
// should *not* use the IDictionary<K,V>
Log("Dictionary", dict);
// now try the EXACT type
IDictionary<string, int> idict = fooDict;
// should use the IDictionary<K,V> - does
Log("IDictionary", idict);
注释描述了代码的调用执行情况,为什么会有这样的结果?
作任何操作执行之前,编译器会先寻找所有合适的候选方法,那些候选方法可能包括泛型方法。如果方法调用不指定参数具体类型,编译器就会推断参数类型。Eric Lippert在说明的解释已经很详细了(如下)
类型推断规则被设计成回答一个问题:仅给定实参和形式参数类型,什么是最合适的实参被推断成每个类型参数。
对于第一个重载方法,Log<string, FooDict>是推断出最匹配的参数类型。现在分析下重载的运算规则,有两个候选方法。使用类型推断,第一个重载方法更匹配,因为调用第一个重载方法是确定转换:Log<string, FooDict> 是一个来自于Log<string, FooDict>的确定转换,调用第二个重载方法则是一个继承转换,Log<string, FooDict> 到Log<string, IDictionary<string, int>>的继承转换,因为FooDict类继承IDictionary<string, int>接口。确定转换优先于接口转换。
同样的处理逻辑可以应用于其它的继承转换:比如转换成父类,长度转换(如int类型转成long类型)或者用户定义类继承转换。
引用规范和说”就是这样工作运行的”是一样的,但是为什么这是正确的选择对于编程语言来说呢?简略地说,因为接口类型在真正编译的时候类型转换才起作用(注:泛型是生成后就已经起作用了)。 我完成的部分已经发布到这里 C# Puzzlers Live Lessons。
以下是本人写的完整的测试运行代码
class Program
{static void Main(string[] args){// create custom dictionary
FooDict fooDict = new FooDict();
// should *not* use the IDictionary<K,V>
Logger.Log("FooDict", fooDict);
// try a regular dictionary value
Dictionary<string, int> dict = fooDict;// should *not* use the IDictionary<K,V>
Logger.Log("Dictionary", dict);
// now try the EXACT type
IDictionary<string, int> idict = fooDict;// should use the IDictionary<K,V> - does
Logger.Log("IDictionary", idict);
}}class FooDict : Dictionary<string, int>{}class Logger
{public static void Log<K, V>(string key, IDictionary<K, V> values){Console.WriteLine("call:Log<K, V>(string key, IDictionary<K, V> values)");
}public static void Log<K, V>(K key, V value){Console.WriteLine("call:Log<K,V>(K key, V value)");
}}