原文地址: http://www.srtsolutions.com/generics-the-better-method-match?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+billwagner+%28Bill+Blogs+in+C%23%29
這個問題又出現在我參與分享的一個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)"); }
}