2013-03-22 283 views
19
class Class1<T> 
{ 
    public virtual void Update(T entity) 
    { 
     Update(new List<T>() { entity }); //It's failed 
    } 

    public virtual void Update(IEnumerable<T> entities) 
    { 
    } 

    public virtual void Update<TSub>(TSub entity) where TSub : T 
    { 
    } 

    public virtual void Update<TSub>(IEnumerable<TSub> entities) where TSub : T 
    { 
    } 
} 

我有一段代碼沒有隱式引用轉換。但它總是失敗。有從「System.Collections.Generic.List <T>」到「T」

如果我用Update((new List<T>() { entity }).AsEnumerable())代替Update(new List<T>() { entity }),它會沒事的。

刪除第三種方法Update<TSub>(TSub entity) where TSub : T也可以。

有人可以告訴我爲什麼嗎?

+7

您闖入了C#規範的黑暗部分 - 重載分辨率。結合泛型,'params',繼承,多態性,通用約束,'dynamic',可選參數 - 使我的聽力率提高http://msdn.microsoft.com/en-us/library/aa691336(v=vs.71 )的.aspx。 +1爲好問題btw – 2013-03-22 10:50:26

回答

2

你實質上是在問爲什麼編譯器沒有創建從List<T>IEnumerable<T>的隱式轉換。原因在於C#團隊做出了一個慎重的設計決定,即潛在模糊性的案例必須由程序員解決,而不是由編譯器解決。 (請注意,VB.NET團隊做出了不同的決定,總是試圖理智的東西是有感知的程序員意圖一致)

像這樣的情況下的優點是驚喜最小化 - 沒有什麼意外都可能發生在封面下;缺點是偶爾需要更詳細的代碼。

+1

它實際上是從'List '轉換成'IEnumerable ' – Rik 2013-03-22 11:34:47

12

約束不是簽名的一部分,Eric Lippert對此主題有很好的article

+0

+1。我找不到那篇文章。 – 2013-03-22 11:14:29

17

好的,我們仔細研究一下。我們有

Update(new List<T>()); 

而三位候選人 - 注意,我們只關心簽名的考生,所以我們將剝去的返回類型和限制,這是未簽名的一部分:

Update(IEnumerable<T> entities) 
Update<U>(U entity) 
Update<V>(IEnumerable<V> entities) 

我們的首要任務是做類型推斷這些最後兩個人選。如果推論失敗,那麼他們不適用。

考慮第二種方法

Update<U>(U entity) 

我們List<T>類型的參數和形式參數U。因此我們推斷UList<T>

考慮第三種方法:

Update<V>(IEnumerable<V> entities) 

我們List<T>類型的參數和IEnumerable<V>類型的形式參數。 List<T>實現IEnumerable<T>因此我們推斷,V是T.

OK,所以我們的候選人名單目前包括:

Update(IEnumerable<T> entities) 
Update<List<T>>(List<T> entity) 
Update<T>(IEnumerable<T> entities) 

是否所有這些候選人適用的?是。在每種情況下,List<T>都可以轉換爲正式的參數類型。我們無法消除其中的任何一個。

既然我們只有適用的候選人,我們必須確定哪一個是唯一最好的

我們可以立即消除第三個。第三個和第一個在它們的形式參數列表中是相同的。 C#的規則是當你在形式參數列表中有兩個相同的方法時,其中一個「自然地」到達那裏,其中一個通過類型替換到達那裏,被替換的那個丟失。

我們也可以消除第一個。顯然,第二個中的精確匹配比第一個中的精確匹配要好。

這留下第二個人作爲最後一個人站立。它贏得重載分辨率的戰鬥。然後在最終驗證期間,我們發現違反了約束條件:List<T>不保證是派生類T

因此重載解析失敗。你的論點導致選擇的最好的方法是無效的。

如果我打電話Update((new List<T>() { entity }).AsEnumerable()),它會沒事的。

正確的。再次通過它。三名候選人:

Update(IEnumerable<T> entities) 
Update<U>(U entity) 
Update<V>(IEnumerable<V> entities) 

我們IEnumerable<T>類型的參數,所以我們推斷第二和第三個是:

Update(IEnumerable<T> entities) 
Update<IEnumerable<T>>(IEnumerable<T> entity) 
Update<T>(IEnumerable<T> entities) 

現在我們有相同的參數列出了三個候選人適用。那些在建的地方自然比自然地方差,所以我們排除了第二和第三,只剩下第一個。它贏了,它沒有任何限制被侵犯。

過,當你刪除第三個方法

你的說法是錯誤的馬上就好了;這將產生與第一種情況相同的錯誤。將第三位候選人取走不會導致第一位候選人突然開始擊敗第二位候選人。

+0

我仍然不明白爲什麼編譯器需要「猜測」,如果我們讓約束成爲方法簽名的一部分。編譯器檢查第三種方法(更新(...)),然後推斷通用類型爲List ,然後發現列表不是T,則消除第三個方法。 – 2013-03-23 05:29:20

+0

@MonghongLin:參見Andrew的回答中引用的文章。我試圖解釋爲什麼限制作爲簽名的一部分,使得超負荷解決的結果變得更糟,幾乎沒有人同意我的觀點。 – 2013-03-23 06:36:31

+0

@ eric-lippert,謝謝你的回覆。現在對我來說很清楚。但我還有一個問題。 '刪除第三種方法時也可以。'當我完全刪除第三種方法時,我不會收到錯誤。你能告訴我理由嗎? – 2013-03-25 06:19:01

相關問題