问题描述
我有两个业务合同类:
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;
}
上面的代码甚至无法编译,这让我有点困惑.我将 T 限制为 BusinessContract,那么为什么编译器不知道 bar 可以分配给 _foo?
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" );
应用程序在运行时因 InvalidCastException 而崩溃.
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?
更新
乔恩回答了这个问题,因此得到了点头,但只是为了结束这个循环,这就是我们最终解决问题的方法.
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 );
}
我们为什么要这样做?我们有一个用于单元测试的假 DAL.使用其中一种方法,我们需要让测试开发人员能够指定在测试期间调用该方法时应该做什么(它是一种从数据库更新缓存对象的刷新方法).Foo 的目的是设置调用 refresh 时应该发生什么.IOW,在本课程的其他地方,我们有以下内容.
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 );
}
}
然后,例如,测试开发人员可以决定在调用 Refresh 时将名称设置为不同的值.
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" );
推荐答案
你把协变和逆变弄错了.让我们考虑 Action<object>
和 Action<string>
.删除实际的泛型,您正在尝试执行以下操作:
You've got the covariance and contravariance the wrong way round. Let's consider Action<object>
and Action<string>
. Removing the actual generics, you're trying to do something like this:
private Action<object> _foo;
public void Foo(Action<string> bar)
{
// This won't compile...
_foo = bar;
}
现在假设我们接着写:
_foo(new Button());
这很好,因为 Action<object>
可以被传递 any 对象...但是我们已经用一个 必须 的委托对其进行了初始化> 接受一个字符串参数.哎哟.
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:
private Action<string> _foo;
public void Foo(Action<object> bar)
{
// This is fine...
_foo = bar;
}
现在当我们调用 _foo
时,我们必须 传入一个字符串 - 但这很好,因为我们已经用一个可以接受任何 object
引用作为参数,所以我们可以给它一个字符串.
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 any object
reference as a parameter, so it's fine that we happen to be giving it a string.
所以基本上 Action<T>
是 逆变 - 而 Func
So basically Action<T>
is contravariant - whereas Func<T>
is covariant:
Func<string> bar = ...;
Func<object> foo = bar; // This is fine
object x = foo(); // This is guaranteed to be okay
不清楚你想用这个动作做什么,所以很遗憾,我无法就如何解决这个问题提供任何建议......
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...
这篇关于动作委托、泛型、协变和逆变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!