2013-03-25 66 views
8

我有一些無法理解爲什麼下面的代碼片段並沒有給我一個錯誤行動泛型約束不能按預期工作

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

但是這樣一來,這是我所期望的,由於一般工作類型約束

private readonly IList<Action<MyInterface>> myActionList = new List<Action<MyInterface>>(); 

public IDisposable Subscribe<T>(Action<T> callback) where T: MyInterface 
{ 
    myActionList.Add(callback); // doesn't compile 
    return null 
} 

給出了這樣的錯誤

cannot convert from 'System.Action<T>' to 'System.Action<MyInterface>' 

我使用VS2012 SP1和.NET 4.5。

任何人都可以解釋爲什麼約束不允許這樣編譯?

+1

爲什麼您的列表只讀,爲什麼'新的IList'?這是實際的宣言嗎? – 2013-03-25 09:03:29

+1

班級和代表不是一回事。 'System.Action '代表具有'MyInterface'類型的單個參數的函數,而'System.Action '代表具有'T:MyInterface'類型參數的方法。函數簽名是不兼容的,'T'是'MyInterface'的衍生物並不相關,只有'T'完全是'MyInterface'時,簽名纔會兼容。 – 2013-03-25 09:04:13

+0

@PaoloTedesco道歉,它從一些其他代碼重建和簡化。複製/粘貼錯誤 – 2013-03-25 09:18:27

回答

3

班級和代表是不一樣的。 System.Action<MyInterface>表示具有MyInterface類型的單個參數的函數,而System.Action<T>表示具有類型T : MyInterface的參數的方法。函數簽名是不兼容的,TMyInterface的衍生產品並不相關,只有在T恰好爲MyInterface時,簽名纔會兼容。

0

如果T被限制在一定的接口,無論如何,你可以只使用代替它的是接口:

public void SomeMethod(MyInterface arg) 
{ 
    MyInterface e = arg; 
} 

private readonly IList<Action<MyInterface>> myActionList = new IList<Action<MyInterface>>(); 

public IDisposable Subscribe(Action<MyInterface> callback) 
{ 
    myActionList.Add(callback); // does compile 
    return null 
} 

將工作和編譯,幾乎是一樣的,你現在有什麼。

如果您想要執行相同的操作(無論類型是否相同),那麼如果您將類型限制爲某個接口,則已經擊敗了泛型的目的,並且應該可以僅使用該接口。

+0

我想他希望能夠通過該特定接口的子類型..例如。我的接口:MyInterface – 2013-03-25 09:13:23

+0

@Roger可能是真的,我沒有從OP得到,但我承認這是我沒有考慮的方法。用示例解釋+1, – Bazzz 2013-03-25 09:15:11

4

這是一個逆變問題 - 一個Action<MyInterface>應該能夠採取任何MyInterface實例作爲論據,但是你想存儲的Action<T>其中TMyInterface一些亞型,這是不是安全的。

例如,如果您有:

public class SomeImpl : MyInterface { } 
public class SomeOtherImpl : MyInterface { } 
List<Action<MyInterface>> list; 

list.Add(new Action<SomeImpl>(i => { })); 
ActionMyInterface act = list[0]; 
act(new SomeOtherImpl()); 

如果類型T比類型U「小」你只能分配一個Action<T>一些Action<U>。例如

Action<string> act = new Action<object>(o => { }); 

是安全的,因爲字符串參數在對象參數所在的位置始終有效。

+0

+1。 – nawfal 2013-04-19 19:21:22

1

where T: MyInterface約束裝置「任何類或結構,它實現MyInterface的任何實例」。

所以,你要怎樣做可以簡化爲這樣:

Action<IList> listAction = null; 
Action<IEnumerable> enumAction = listAction; 

這是不應該的工作,同時還IList : IEnumerable。更多詳細信息可以在這裏找到:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx http://msdn.microsoft.com/en-us/library/dd799517.aspx

所以,如果你真的需要使用通用的,只是界面 - 你可以像這樣做,但它增加了複雜性和較小的性能問題:

public static IDisposable Subscribe<T>(Action<T> callback) where T : MyInterface 
{ 
    myActionList.Add(t => callback((T)t)); // this compiles and work 
    return null; 
} 
1

類和代表的行爲有點不同。讓我們看一個簡單的例子:

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

在這種方法中,你可以假設T公司將至少爲MyInterface,所以你可以做這樣的事情MyInterface e = arg;因爲ARGS總是可以被轉換爲MyInterface

現在讓我們看看代表的行爲:

public class BaseClass { }; 
public class DerivedClass : BaseClass { }; 
private readonly IList<Action<BaseClass >> myActionList = new List<Action<BaseClass>>(); 

public void Subscribe<T>(Action<T> callback) where T: BaseClass 
{ 
    myActionList.Add(callback); // so you could add more 'derived' callback here Action<DerivedClass> 
    return null; 
} 

現在我們來抓加入DerivedClass回調myActionList,然後放到你調用代表:

foreach(var action in myActionList) { 
    action(new BaseClass); 
} 

但你不能這樣做,因爲如果您有DerivedClass回調,則必須將其作爲參數傳遞給DerivedClass。

此問題指Covariance and contravariance。您可以閱讀關於this文章的差異,Eric Lippert也有非常多的有關差異的文章,this是第一篇文章,您可以在他的博客中找到剩餘的文章。

P.S.編輯贊同李評論。

+0

類不能協變 - 只有委託和接口可以有差異註釋。說代表們'具有'矛盾也是誤導 - 'Func'委託類型在他們的論點中是逆變的,在他們的返回類型中是協變的。反變換不限於委託類型,例如,參見'IObserver '接口,它在'T'中是逆變的。 – Lee 2013-03-25 09:58:49

+0

我編輯了我的帖子,謝謝。 – Andrew 2013-03-25 11:53:20

2

我發現它有助於在這些情況下考慮出現問題,如果您允許這種行爲。所以讓我們考慮一下。

interface IAnimal { void Eat(); } 
class Tiger : IAnimal 
{ 
    public void Eat() { ... } 
    public void Pounce() { ... } 
} 
class Giraffe : IAnimal 
... 
public void Subscribe<T>(Action<T> callback) where T: IAnimal 
{ 
    Action<IAnimal> myAction = callback; // doesn't compile but pretend it does. 
    myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal 
} 
... 
Subscribe<Tiger>((Tiger t)=>{ t.Pounce(); }); 

那麼會發生什麼?我們創建一個需要老虎並跳躍的代表,將其傳遞給Subscribe<Tiger>,將其轉換爲Action<IAnimal>,然後傳遞一個長頸鹿,然後再跳躍。

很明顯,這是非法的。唯一合理的做法是將Action<Tiger>轉換爲Action<IAnimal>。所以這就是違法的地方。