动作委托、泛型、协变和逆变
我有两个业务合同类:
public BusinessContract
public Person : BusinessContract
在另一个类中,我有以下代码:
private Action<BusinessContract> _foo;
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = bar;
}
上面的代码甚至无法编译,这让我有点困惑。我将 T 限制为 BusinessContract,那么为什么编译器不知道 bar 可以分配给 _foo 呢?
为了解决这个问题,我们尝试将其更改为以下内容:
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = (Action<BusinessContract>)bar;
}
现在编译器很高兴,因此我在应用程序的其他位置编写了以下代码:
Foo<Person>( p => p.Name = "Joe" );
应用程序在运行时因 InvalidCastException 而崩溃。
我不明白。我是否应该能够将更具体的类型转换为不太具体的类型并分配它?
更新
乔恩回答了这个问题,所以得到了点头,但为了结束这个循环,这就是我们最终解决问题的方式。
private Action<BusinessContract> _foo;
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = contract => bar( (T)contract );
}
我们为什么要这样做?我们有一个用于单元测试的 Fake DAL。使用其中一种方法,我们需要让测试开发人员能够指定在测试期间调用该方法时应该执行的操作(这是一种刷新方法,用于更新数据库中的缓存对象)。 Foo 的目的是设置调用刷新时应该发生的情况。 IOW,在本课程的其他地方,我们有以下内容。
public void Refresh( BusinessContract contract )
{
if( _foo != null )
{
_foo( contract );
}
}
例如,测试开发人员可以决定在调用 Refresh 时将名称设置为不同的值。
Foo<Person>( p => p.Name = "New Name" );
I have two business contract classes:
public BusinessContract
public Person : BusinessContract
In another class I have the following code:
private Action<BusinessContract> _foo;
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = bar;
}
The above won't even compile, which baffles me a bit. I'm constraining T to be BusinessContract, so why doesn't the compiler know that bar can be assigned to _foo?
In trying to get around this, we tried changing it to the following:
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = (Action<BusinessContract>)bar;
}
Now the compiler is happy, so I write the following code elsewhere in my application:
Foo<Person>( p => p.Name = "Joe" );
And the app blows up with an InvalidCastException at run-time.
I don't get it. Shouldn't I be able to cast my more specific type to a less specific type and assign it?
UPDATE
Jon answered the question so got the nod for that, but just to close the loop on this, here's how we ended up solving the problem.
private Action<BusinessContract> _foo;
public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
_foo = contract => bar( (T)contract );
}
Why are we doing this? We have a Fake DAL we use for unit testing. With one of the methods we need to give the test developer the ability to specify what the method should do when it's called during the test (it's a refresh method that updates a cached object from the database). The purpose of Foo is to set what should happen when refresh is called. IOW, elsewhere in this class we have the following.
public void Refresh( BusinessContract contract )
{
if( _foo != null )
{
_foo( contract );
}
}
The test developer could then, for example, decide they wanted to set the name to a different value when Refresh was called.
Foo<Person>( p => p.Name = "New Name" );
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你把协变和逆变搞错了。让我们考虑一下
Action
现在假设我们然后编写:
没关系,因为
Action
这不是类型安全的,因此无法编译。
另一种方式会工作:
现在,当我们调用
_foo
时,我们必须传入一个字符串 - 但这很好,因为我们已经使用委托初始化它,该委托可以将任何对象引用作为参数,因此我们碰巧给它一个字符串就可以了。所以基本上
Action
是逆变 - 而Func
是协变:目前尚不清楚您要做什么正在尝试执行此操作,因此不幸的是,我无法就如何解决此问题提供任何建议......
You've got the covariance and contravariance the wrong way round. Let's consider
Action<object>
andAction<string>
. Removing the actual generics, you're trying to do something like this:Now suppose we then write:
That's fine, because
Action<object>
can be passed any object... but we've initialized it with a delegate which must take a string argument. Ouch.This isn't type safe, so doesn't compile.
The other way would work though:
Now when we invoke
_foo
, we have to pass in a string - but that's fine, because we've initialized it with a delegate which can take anyobject
reference as a parameter, so it's fine that we happen to be giving it a string.So basically
Action<T>
is contravariant - whereasFunc<T>
is covariant:It's not clear what you're trying to do with the action, so unfortunately I can't really give any advice on how to get around this...
它无法被分配,因为由于您使用的是逆变而不是协变,因此无法保证泛型类型可以分配给 foo。
It cannot be assigned because since you are using a contravariant instead of a covariant, there is no way to guarantee that the generic type can be assigned to foo.