行动代表,仿制药,协方差和逆变
我有两个商业合同类:
public BusinessContract public Person : BusinessContract
在另一个类中,我有以下代码:
private Action _foo; public void Foo( Action bar ) where T : BusinessContract { _foo = bar; }
以上甚至都不会编译,这让我感到困惑。 我将T限制为BusinessContract,为什么编译器不知道bar可以分配给_foo?
为了解决这个问题,我们尝试将其更改为以下内容:
public void Foo( Action bar ) where T : BusinessContract { _foo = (Action)bar; }
现在编译器很高兴,所以我在我的应用程序的其他地方编写了以下代码:
Foo( p => p.Name = "Joe" );
该应用程序在运行时因InvalidCastException而爆炸。
我不明白。 我不应该能够将更具体的类型转换为不太具体的类型并分配它吗?
UPDATE
乔恩回答了这个问题,所以得到了点头,但只是为了关闭循环,这就是我们最终解决问题的方法。
private Action _foo; public void Foo( Action bar ) where T : BusinessContract { _foo = contract => bar( (T)contract ); }
我们为什么这样做? 我们有一个用于unit testing的假DAL。 使用其中一种方法,我们需要让测试开发人员能够指定在测试期间调用方法时应该执行的操作(这是一种从数据库更新缓存对象的刷新方法)。 Foo的目的是设置调用刷新时应该发生的事情。 IOW,在本课程的其他地方,我们有以下内容。
public void Refresh( BusinessContract contract ) { if( _foo != null ) { _foo( contract ); } }
例如,测试开发人员可以决定在调用Refresh时将名称设置为不同的值。
Foo( p => p.Name = "New Name" );
你的协方差和逆变是错误的。 让我们考虑Action
和Action
。 删除实际的generics,你试图做这样的事情:
private Action
现在假设我们写:
_foo(new Button());
这没关系,因为Action
可以传递给任何对象……但是我们用一个必须带字符串参数的委托来初始化它。 哎哟。
这不是类型安全的,所以不编译。
另一种方式可行:
private Action _foo; public void Foo(Action
现在当我们调用_foo
,我们必须传入一个字符串 – 但这没关系,因为我们用一个委托来初始化它,它可以将任何object
引用作为参数,所以我们碰巧给它一个字符串。
所以基本上Action
是逆变的 – 而Func
是协变的 :
Func bar = ...; Func
目前还不清楚你要对这个动作做些什么,所以不幸的是我无法就如何解决这个问题给出任何建议……
它无法分配,因为由于您使用逆变而不是协变,因此无法保证可以将generics类型分配给foo。