2012-01-18 55 views
10

我不清楚爲什麼下面的代碼段不是covarient?爲什麼C#out泛型類型參數違反協方差?

public interface IResourceColl<out T> : IEnumerable<T> where T : IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    bool TryGetValue(string SUID, out T obj); // Error here? 
    } 

錯誤1無效方差:類型參數 'T' 必須是 'IResourceColl.TryGetValue(字符串,出T)' 不變地 有效。 'T'是 covariant。

我的界面只在輸出位置使用模板參數。我可以很容易地重構這個代碼,以類似

public interface IResourceColl<out T> : IEnumerable<T> where T : class, IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    T TryGetValue(string SUID); // return null if not found 
    } 

,但我想了解,如果我原來的代碼實際上違反了協方差或者如果這是協方差的編譯器或.NET的限制。

+0

的可能重複[C#:爲什麼不呢?做「裁判」和「出」支持多態性(HTTP://計算器。 COM /問題/ 1207144/C鋒利爲什麼-犯規,裁判和出支持多態) – Jon 2012-01-18 16:43:37

+0

這裏要注意的重要一點是,['out'](http://msdn.microsoft.com/en -us /庫/ ee332485.aspx)(參數改性劑)是完全無關的(http://msdn.microsoft.com/en-us/library/dd469487.aspx)(使用[out'']在通用型參數)。 – Jon 2012-01-18 16:44:50

+0

@Jon - 這個問題適用於C#3.0和之前的版本。這裏描述的語法是C#4.0 – Oded 2012-01-18 16:44:57

回答

11

的問題確實是在這裏:

bool TryGetValue(string SUID, out T obj); // Error here? 

你標記的obj爲out參數,這仍然意味着儘管這你在obj傳遞所以它不可能是協變的,因爲你們倆傳中的實例鍵入T並返回。

編輯:

埃裏克利珀說,它比任何人我指的是his answer to "ref and out parameters in C# and cannot be marked as variant"並引述他的問候out參數更好:

它應該是合法的,將T標記爲「走出去」 ?很不幸的是,不行。 「out」 實際上與幕後的「ref」沒有區別。 「out」和「ref」之間唯一的 區別在於編譯器禁止 在被調用者分配之前從out參數中讀取數據,並且 在調用者正常返回 之前編譯器需要分配。 有人在C#之外的其他.NET語言中編寫了此接口的實現,他們可以在 初始化之前讀取該項目,因此它可以用作輸入。因此,我們 禁止在此情況下將T標記爲「出」。這很遺憾, 但我們無能爲力;我們必須遵守CLR的類型安全規則 。

+0

我可能會傳入除T之外的其他內容,但C#規則要求我在分配內容之前先指定一些內容,這樣它永遠不會導致問題。 – MerickOWA 2012-01-18 17:00:41

+0

啊我現在看到,有可能以其他語言讀取價值,而eric的博客甚至在C#中指出可能會破壞的情況。這就解釋了爲什麼協變違反了。 – MerickOWA 2012-01-18 17:25:25

+0

問題是,所謂的'out'參數確實不是;一個真實的'out'參數會導致編譯器爲函數的返回生成一個結構類型,這將包括指定的返回類型和所有'out'參數;調用者會自動將結構中的相應字段複製到out參數中,然後將剩餘字段(如果有)作爲返回值。如果以這種方式實施參數,它們確實可以是協變的。 – supercat 2012-06-28 18:08:53

1

它違反協方差,因爲提供給輸出參數的值必須是恰好相同類型作爲輸出參數聲明。舉例來說,假設T是一個字符串,協方差將意味着這將是確定做

var someIResourceColl = new someIResourceCollClass<String>(); 
Object k; 
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String 
1

檢查這個小例子,你就會明白爲什麼它是不允許的:

public void Test() 
{ 
    string s = "Hello"; 
    Foo(out s); 
} 

public void Foo(out string s) //s is passed with "Hello" even if not usable 
{ 
    s = "Bye"; 
} 

out意味着s必須在執行離開該方法之前明確分配,相反,只有在方法體中明確賦值後才能使用s。這似乎與協方差規則兼容。但是在調用方法之前,沒有任何東西阻止您在呼叫站點分配s。該值被傳遞給這意味着該方法,即使它不是可用你有效地傳遞一個定義類型的方法的參數,這違背協方差的規則,其中指出泛型類型只能是用作方法的返回類型。

3

下面是一個使用擴展方法的可行的解決方法。不一定是從的觀點實施者點方便,但用戶應該感到高興:

public interface IExample<out T> 
{ 
    T TryGetByName(string name, out bool success); 
} 

public static class HelperClass 
{ 
    public static bool TryGetByName<T>(this IExample<T> @this, string name, out T child) 
    { 
     bool success; 
     child = @this.TryGetByName(name, out success); 
     return success; 
    } 
} 

public interface IAnimal { }; 

public interface IFish : IAnimal { }; 

public class XavierTheFish : IFish { }; 

public class Aquarium : IExample<IFish> 
{ 
    public IFish TryGetByName(string name, out bool success) 
    { 
     if (name == "Xavier") 
     { 
      success = true; 
      return new XavierTheFish(); 
     } 
     else 
     { 
      success = false; 
      return null; 
     } 
    } 
} 

public static class Test 
{ 
    public static void Main() 
    { 
     var aquarium = new Aquarium(); 
     IAnimal child; 
     if (aquarium.TryGetByName("Xavier", out child)) 
     { 
      Console.WriteLine(child); 
     } 
    } 
} 
相關問題