我无法理解为什么以下代码段不会给我一个错误
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。
有人可以解释为什么约束不允许这个编译吗?
答案 0 :(得分:5)
这是一个逆转问题 - Action<MyInterface>
应该可以将任何MyInterface
个实例作为参数,但是您尝试将Action<T>
存储在T
的位置MyInterface
的某个子类型,这是不安全的。
例如,如果你有:
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());
如果Action<T>
类型比“{1}}类型小”,则只能为Action<U>
T
分配U
。例如
Action<string> act = new Action<object>(o => { });
是安全的,因为字符串参数在对象参数为的地方始终有效。
答案 1 :(得分:3)
类和委托不是一回事。 System.Action<MyInterface>
表示具有MyInterface
类型的单个参数的函数,而System.Action<T>
表示具有类型T : MyInterface
的参数的方法。功能签名不兼容,T
是MyInterface
的衍生产品并不相关,只有当T
正好MyInterface
时,签名才会兼容。
答案 2 :(得分: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>
。这就是非法的地方。
答案 3 :(得分: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;
}
答案 4 :(得分: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。编辑李的评论。
答案 5 :(得分: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
}
将工作和编译,几乎与你现在的相同。
如果你想对类型的REGARDLESS做同样的操作,那么泛型是很有用的,如果你把类型限制到某个接口你已经打败了泛型的目的,那么应该只使用那个接口。