Action的通用约束无法按预期工作
我无法理解为什么以下代码段不会给我一个错误
public void SomeMethod(T arg) where T : MyInterface { MyInterface e = arg; }
但是这个,我期望由于generics类型约束而工作
private readonly IList<Action> myActionList = new List<Action>(); public IDisposable Subscribe(Action callback) where T: MyInterface { myActionList.Add(callback); // doesn't compile return null }
给出了这个错误
cannot convert from 'System.Action' to 'System.Action'
我正在使用VS2012 sp1和.NET 4.5。
任何人都可以解释为什么约束不允许这个编译?
类和委托不是一回事。 System.Action
表示具有MyInterface
类型的单个参数的函数,而System.Action
表示具有类型T : MyInterface
的参数的方法T : MyInterface
。 函数签名是不兼容的, T
是MyInterface
的衍生物并不相关,只有当T
恰好是MyInterface
,签名才会兼容。
这是一个逆转问题 – 一个Action
应该能够将任何MyInterface
实例作为参数,但是你试图存储一个Action
,其中T
是MyInterface
某个子类型,这是不安全的。
例如,如果你有:
public class SomeImpl : MyInterface { } public class SomeOtherImpl : MyInterface { } List> list; list.Add(new Action(i => { })); ActionMyInterface act = list[0]; act(new SomeOtherImpl());
如果类型T
比类型U
“小”,则只能将Action
分配给某个Action
。 例如
Action act = new Action
是安全的,因为字符串参数在对象参数所在的位置始终有效。
我发现在这些情况下,如果允许这种行为,可以考虑出现问题。 所以让我们考虑一下。
interface IAnimal { void Eat(); } class Tiger : IAnimal { public void Eat() { ... } public void Pounce() { ... } } class Giraffe : IAnimal ... public void Subscribe(Action callback) where T: IAnimal { Action myAction = callback; // doesn't compile but pretend it does. myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal } ... Subscribe((Tiger t)=>{ t.Pounce(); });
那会发生什么? 我们创建了一个代表老虎和扑克的委托,将其传递给Subscribe
,将其转换为Action
,然后传递长颈鹿,然后猛扑。
显然这必须是非法的。 唯一明智的做法是将Action
转换为Action
。 这就是非法的地方。
where T: MyInterface
约束意味着“实现MyInterface的任何类或结构的任何实例”。
所以你要做的事情可以简化为:
Action listAction = null; Action enumAction = listAction;
哪个不应该工作,而仍然是IList : IEnumerable
。 更多详情可在这找到:
https://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx https://msdn.microsoft.com/en-us/library/dd799517.aspx
因此,如果你真的需要使用generics而不仅仅是接口 – 你可以这样做,虽然它增加了复杂性和轻微的性能问题:
public static IDisposable Subscribe(Action callback) where T : MyInterface { myActionList.Add(t => callback((T)t)); // this compiles and work return null; }
类和委托的行为略有不同。 让我们看一个简单的例子:
public void SomeMethod(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> myActionList = new List>(); public void Subscribe(Action callback) where T: BaseClass { myActionList.Add(callback); // so you could add more 'derived' callback here Action return null; }
现在我们将DerivedClass回调添加到myActionList,然后在某处调用委托:
foreach( var action in myActionList ) { action(new BaseClass); }
但是你不能这样做,因为如果你有DerivedClass回调,你必须将它传递给DerivedClass作为参数。
这个问题涉及协方差和逆变 。 你可以从这篇文章中读到关于方差的文章,Eric Lippert也有关于方差的非常有趣的文章, 这是第一篇文章,你可以在他的博客中找到其余文章。
PS编辑了李评论。
如果T
无论如何都限于某个接口,你可以直接使用该接口:
public void SomeMethod(MyInterface arg) { MyInterface e = arg; } private readonly IList> myActionList = new IList>(); public IDisposable Subscribe(Action callback) { myActionList.Add(callback); // does compile return null }
将工作和编译,几乎与你现在的相同。
如果你想在类型上执行相同的操作REGARDLESS,那么generics是很有用的,如果你将类型限制为某些接口,你已经打败了generics的目的,而应该只使用那个接口。
上述就是C#学习教程:Action的通用约束无法按预期工作分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/983710.html