General restriction for action not working properly

I am having trouble understanding why the following snippet doesn't give me an error

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

But this one that I would expect to work due to a general type restriction

 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 } 

Gives this error

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

I am using VS2012 sp1 and .NET 4.5.

Can someone explain why the restriction prevents compiling?

+8
generics c # covariance contravariance
source share
6 answers

Classes and delegates are not the same thing. System.Action<MyInterface> represents a function with a single parameter of type MyInterface , and System.Action<T> represents a method with a parameter of type T : MyInterface . Function signatures are incompatible, it does not matter that T is derived from MyInterface , the signature will only be compatible if T was exactly MyInterface .

+3
source share

This is the problem of contraception. Action<MyInterface> should accept any instance of MyInterface as an argument, however you are trying to save Action<T> , where T is some subtype of MyInterface that is not safe.

For example, if you have:

 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()); 

You can assign an Action<T> to some Action<U> if type T less than type U for example

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

is safe because the string argument is always valid if the argument of the object.

+4
source share

I find it useful in these situations to think about what goes wrong if you allow behavior. Therefore, keep this in mind.

 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(); }); 

So what is going on? We create a delegate that takes the tiger and pounces, pass it a Subscribe<Tiger> , convert it to an Action<IAnimal> and pass a giraffe, which then Action<IAnimal> out.

Obviously, this should be illegal. The only place where it makes sense to make it illegal is the conversion from Action<Tiger> to Action<IAnimal> . So where is it illegal.

+2
source share

The where T: MyInterface means " any instance of any class or structure that implements MyInterface."

So what you are trying to do can be simplified as follows:

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

Which should not work, as well as IList : IEnumerable . More details can be found here:

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

So, if you really have to use a common, not just an interface, you can do it this way, although this adds complexity and minor performance issues:

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

Classes and delegates behave a little differently. Consider a simple example:

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

In this method, you could assume that T will be at least MyInterface , so you can do something like this MyInterface e = arg; , because args can always be added to MyInterface .

Now let's see how delegates behave:

 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; } 

Now we add the DerivedClass callback to myActionList, and then you call delegates somewhere:

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

But you cannot do this, because if you have a DerivedClass callback, you must pass it as DerivedClass as a parameter.

This question relates to covariance and contravariance . You can read about the deviation from this article, also Eric Lippert has some very interesting articles on variance, this is the first article, you can find the rest on his blog.

PS Edited commentary on Lee's commentary.

+1
source share

If T limited to a specific interface, you can simply use this interface instead of it:

 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 } 

It will work and compile and is practically no different from what you have.

Turnovers are useful if you want to do the same REGARDLESS operation of this type, if you then restrict the type to an interface, you defeat the target of the generics and probably should just use that interface.

0
source share

All Articles