行动代表,仿制药,协方差和逆变

时间:2011-08-01 21:41:13

标签: c# delegates

我有两个商业合同类:

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而爆炸。

我不明白。我不应该能够将更具体的类型转换为不太具体的类型并分配它吗?

更新

Jon回答了这个问题,所以得到了点头,但只是为了关闭循环,这就是我们最终解决问题的方法。

private Action<BusinessContract> _foo;

public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
    _foo = contract => bar( (T)contract );
}

我们为什么要这样做?我们有一个假DAL,我们用于单元测试。使用其中一种方法,我们需要让测试开发人员能够指定在测试期间调用方法时应该执行的操作(这是一种从数据库更新缓存对象的刷新方法)。 Foo的目的是设置调用刷新时应该发生的事情。 IOW,在本课程的其他地方我们有以下内容。

public void Refresh( BusinessContract contract )
{
    if( _foo != null )
    {
        _foo( contract );
    }
}

例如,测试开发人员可以决定在调用Refresh时将名称设置为不同的值。

Foo<Person>( p => p.Name = "New Name" );

2 个答案:

答案 0 :(得分:16)

你的协方差和逆变是错误的方式。我们考虑Action<object>Action<string>。删除实际的泛型,你试图做这样的事情:

private Action<object> _foo;

public void Foo(Action<string> bar)
{
    // This won't compile...
    _foo = bar;
}

现在假设我们写下:

_foo(new Button());

没关系,因为Action<object>可以传递任何对象......但是我们已经用<​​em>必须获取字符串参数的委托初始化它。哎哟。

这不是类型安全的,所以不编译。

的另一种方式可以工作:

private Action<string> _foo;

public void Foo(Action<object> bar)
{
    // This is fine...
    _foo = bar;
}

现在,当我们调用_foo时,我们传入一个字符串 - 但这没关系,因为我们已经使用委托对其进行了初始化,可以使用任何object引用作为参数,所以我们碰巧给它一个字符串。

所以基本上Action<T>逆变 - 而Func<T>协变

Func<string> bar = ...;
Func<object> foo = bar; // This is fine
object x = foo(); // This is guaranteed to be okay

目前还不清楚你正在尝试这个动作,所以不幸的是我无法就如何解决这个问题给出任何建议......

答案 1 :(得分:0)

无法分配,因为您使用逆变而不是协变,因此无法保证可以将泛型类型分配给foo。