C#中代表的协方差/不变性/逆差异

时间:2014-01-01 15:35:15

标签: c#

我有以下代码。我没有为此委托指定任何通用参数和IN / OUT(方差)。如果我正确理解了不变性的含义,我就不能返回Base类型的对象,因为我的委托提到了对象的返回类型。

我对不变性的理解是错误的吗?

class Program
{
    public delegate object SampleDelegate(Base b);

    static void Main(string[] args)
    {
        List<Base> listBases = new List<Base>(){new Base{}, new Base{}};
        SampleDelegate newDel = new SampleDelegate(ProcessBase);
        newDel(new Base() { });
        Console.ReadLine();
    }

    public static Base ProcessBase(Base b)
    {
        return b;
    }

    public class Base
    {

    }

    public class Derived : Base
    {
    }   
}

2 个答案:

答案 0 :(得分:10)

  

如果我正确理解了不变性的含义,我就不能返回Base类型的对象,因为我的委托提到了对象的返回类型。我对不变性的理解是错误的吗?

由于您可以编译并运行该程序,因此您已经知道该问题的答案。是。

让我们问你想问的问题:

  

由于代表甚至不是通用的,因此代表的明显通用差异不适用。为什么我可以从返回Base的方法到需要方法返回object的委托类型进行协变转换?

显然,通用协方差不是那种相关的协方差;这里有一个完全不同的规则。此转换首先在C#2.0中允许。从方法组转换为委托时,从方法组中选择的方法可能具有比委托的返回类型更通用的返回类型,前提是两种类型都是引用类型。对于参数类型也是如此,它们是逆变的。

允许使用引用类型构造的泛型委托类型之间的转换同样具有协变性和逆变性的特性 - 由我偶然地 - 添加到C#4.0。

答案 1 :(得分:0)

所有代表都允许一定程度的协方差,因为可以为委托分配一个具有更多派生返回类型的方法。这就是你在这里所做的,你有一个返回类型为Base的方法和一个返回类型为object的委托。

基本上,返回的值会在调用时强制转换为object,这将始终有效,因为从Baseobject的强制转换始终有效。

或者换一种方式来看,通过ProcessBase类型的委托致电SampleDelegate就是致电(object)ProcessBase(theArgument)

你可以认为这与我们如何总是使用更多派生类型的参数调用方法相反。例如。我们可以执行ReferenceEquals("abc", 1)因为"abc"可以作为更多派生类型转换为object,并且可以通过装箱将1强制转换为“对象”。

事实上,由于类似的原因,您可以将ProcessBase分配给定义为public delegate object SampleDelegate(Derived b);的委托,因为调用它始终是安全的,因为它只能通过{{1}调用}参数,在调用时总是可以强制转换为Derived

当我们在C#中与代表讨论Basecovariance时,我们更常说的是协变和逆变类型参数。

(主要是因为上面描述的方差类型是2.0以来的语言,以及我将在4.0以后描述的类型,所以后一种类型对已经工作的C#程序员来说是“新闻”)。

如果我们将通用委托定义为:

contravariance

然后考虑以下事项:

public delegate TResult MyDelegate<TArg, TResult>(TArg argument);

我们不允许进行两次转换中的任何一种,但是当你考虑它时,这里可能出现什么问题?

如果我们将MyDelegate<Base, Derived> del = b => null;//simple example. MyDelegate<Derived, Derived> del2 = del; // compiler error 1. CS0029: Cannot implicitly convert type MyDelegate<Base, Base> del3 = del; // compiler error 2. CS0029: Cannot implicitly convert type MyDelegate<Derived, Base> del4 = del; // compiler error 3. CS0029: Cannot implicitly convert type 的定义更改为:

MyDelegate

然后第一个错误就消失了,因为public delegate TResult MyDelegate<in TArg, TResult>(TArg argument); 允许in上的逆转。

如果我们将定义更改为:

TArg

然后第二个错误就消失了,因为public delegate TResult MyDelegate<TArg, out TResult>(TArg argument); 允许out上的协方差。

最后,如果我们将定义更改为:

TResult

然后这三个错误消失了,因为我们同时拥有public delegate TResult MyDelegate<in TArg, out TResult>(TArg argument); in

协方差规则是一种逆转,不允许你指定任何不合逻辑的东西。 E.g:

out

有两个错误:我们不能期望在public delegate TResult MyDelegate<out TArg, in TResult>(TArg argument); 上安全协变,也不能在TArg上安全地逆变;如果我们被允许这样做,我们将被允许将不起作用的代表分配给其他代表类型。

TResultFunc类型提供了此示例。例如,其中一个定义为:

Action

因此我们可以将public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); 分配给Func<object, object, string>类型的变量,因为所涉及的所有调用都可以,但我们无法将Func<string, string, object>分配给{{1}类型的变量},因为这不成立。

通用协方差和逆变也适用于接口,允许我们例如将Func<string, string, object>分配给Func<object, object, string>类型的变量,因为我们可以在IEnumerable<string>上调用的所有内容都可以安全地呼叫IEnumerable<object>