2014-04-22 114 views
5

以下程序無法編譯,因爲在出​​現錯誤的行中,編譯器選擇使用單個參數T作爲分辨率的方法,該參數由於List<T>不符合通用約束條件單個T。編譯器不承認有另一種方法可以使用。如果我刪除單一的方法,編譯器將正確地找到許多對象的方法。通用擴展方法解析失敗

我讀過兩篇關於通用方法分辨率的博客文章,一篇來自JonSkeet here,另一篇來自Eric Lippert here,但我找不到解釋或解決我的問題的方法。

很明顯,有兩種不同名稱的方法可以工作,但我喜歡這種情況下你有一個單一的方法。

namespace Test 
{ 
    using System.Collections.Generic; 

    public interface SomeInterface { } 

    public class SomeImplementation : SomeInterface { } 

    public static class ExtensionMethods 
    { 
    // comment out this line, to make the compiler chose the right method on the line that throws an error below 
    public static void Method<T>(this T parameter) where T : SomeInterface { } 

    public static void Method<T>(this IEnumerable<T> parameter) where T : SomeInterface { } 
    } 

    class Program 
    { 
    static void Main() 
    { 
     var instance = new SomeImplementation(); 
     var instances = new List<SomeImplementation>(); 

     // works 
     instance.Method(); 

     // Error 1 The type 'System.Collections.Generic.List<Test.SomeImplementation>' 
     // cannot be used as type parameter 'T' in the generic type or method 
     // 'Test.ExtensionMethods.Method<T>(T)'. There is no implicit reference conversion 
     // from 'System.Collections.Generic.List<Test.SomeImplementation>' to 'Test.SomeInterface'. 
     instances.Method(); 

     // works 
     (instances as IEnumerable<SomeImplementation>).Method(); 
    } 
    } 
} 
+0

您是否嘗試過'instances.Method ();'? – Dmitry

+0

@Dmitry,確實會工作。我會測試一些東西。 – nvoigt

回答

6

方法解析說,closer is better。查看博客文章,瞭解確切的規則。

更接近什麼意思?編譯器會查看它是否可以找到完全匹配,如果由於某種原因它找不到下一個可能的兼容方法等等。

我們首先通過刪除SomeInterface約束來編譯該方法。

public static class ExtensionMethods 
{ 
    public static void Method<T>(this T parameter) //where T : SomeInterface 
    { } 

    public static void Method<T>(this IEnumerable<T> parameter) //where T : SomeInterface 
    { } 
} 

現在的編譯器是幸福的編譯,也請注意,這兩個方法調用轉到Method(T)而非Method(IEnumerable<T>)。這是爲什麼?

因爲Method(T)更接近於可以採用任何類型作爲參數的意義,也不需要任何轉換。

爲什麼Method(IEnumerable<T>)不近?

這是因爲你有變量的編譯時類型爲List<T>,所以它需要從List<T>IEnumerable<T>引用轉換。哪個更接近,但遠沒有做任何轉換。

回到你的問題。

爲什麼instances.Method();不能編譯?

同樣,正如前面所說的使用Method(IEnumerable<T>)我們需要一些引用轉換,所以很明顯,這不是更近。現在我們只剩下一種非常接近的方法是Method<T>。但問題是你用SomeInterface限制它,並且明確List<SomeImplementation>()不可轉換爲SomeInterface

問題是(猜測)檢查通用約束髮生在編譯器選擇更緊密的過載之後。在這種情況下,這會使所選的最佳過載無效。

您可以通過將變量的靜態類型更改爲IEnumerable<SomeImplementation>來輕鬆修復該變量,該變量將起作用,現在您知道原因了。

IEnumerable<SomeImplementation> instances = new List<SomeImplementation>(); 
+0

好吧,這並不能真正解決我的問題(我仍然必須給出一個不同的名字,以便人們可以毫不驚訝地使用它),但至少這是一個很好的解釋*爲什麼*。 – nvoigt

1

你試過實現不使用泛型第一位的,因爲它應該具有相同的行爲:

public static void Method(this SomeInterface parameter) { /*...*/ } 

或者,正如梅德建議,通過調用第二個方式如下:

instances.Method<SomeImplementation>(); 

但是,您需要在每次通話中添加<SomeImplementation> ...

+0

這是一個好主意。也許我應該擴大我的榜樣。在我們的真實項目中,T被限制爲實現*兩個*不同的接口,這就是我們使用泛型的原因。 – nvoigt

+0

啊,好的。在這種情況下,我的想法不起作用(但對於這種情況會起作用 - 只是測試了它)... – ChrFin

+0

我嘗試了很多東西,無法在兩種情況下都使用泛型,無需調用方法的其他代碼和國際海事組織它應該工作 - 也許別人可以闡明這種行爲(或者它可能真的是一個方法解決方案中的錯誤)... – ChrFin

1
  1. 雖然我知道你不想要它,我認爲你應該真的重新考慮如果方法名稱應該是相同的。我看不出相同的名稱如何作用於實例,並收集這些實例。例如,如果您的方法名稱爲Shoot對於T,那麼另一種方法聽起來應該聽起來像ShootThemAll或類似的東西。

  2. 否則你應該讓你的任務略有不同:

    IEnumerable<SomeImplementation> instances = new List<SomeImplementation>(); 
    instances.Method(); //now this should work 
    
  3. 作爲最後的選擇,因爲在德米特里說的意見,你必須顯式地指定類型參數。

    instances.Method<SomeImplementation>();