为什么协变类型参数仅用于成员的返回类型?

时间:2018-06-12 09:08:22

标签: c# generics delegates

为什么像IEnumerable<out T>类型T这样的协变类型参数仅用于返回类型(只读)或反向逆变类型参数,如Action<in T>类型T仅用作参数类型(只写)? 换句话说,我认为纯协变概念与仅用于成员的返回类型的c#协变类型参数之间存在关系。

2 个答案:

答案 0 :(得分:3)

  

为什么像IEnumerable<out T>这样的协变类型参数的类型T仅用于返回类型?

首先关闭:T 仅用于返回类型。例如:

interface I1<out T>{ T M1(); }
interface I2<out T>{ void M2(Action<I1<T>> a); }

I1<T>中,T仅用于返回类型位置。但在I2中,T用于输入a,而I1<T>Action的输入,因此从某种意义上说它正在被使用在两个输入位置。

但是,让我们考虑一个更简单的案例。为什么我们可以I1中的T协变而不是T中的逆变?

原因是因为协方差是安全的,而逆变是不安全的。我们可以看到协方差是安全的:

class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}
class Giraffe : Mammal {}
class C : I1<Mammal> {
    public Mammal M1() { return new Tiger(); }
}
I1<Mammal> i1m = new C(); // Legal
I1<Animal> i1a = i1m; // Legal
Animal a = i1a.M1(); // Returns a tiger; assigned to animal, good!

无论C.M1返回什么,它始终为Mammal,因此始终为Animal

但这不合法:

I1<Giraffe> i1g = i1m; // Not legal
Giraffe g = i1g.M1(); // Returns a tiger; assigned to giraffe, bad!

第一行必须是非法的,以便第二行永远不会执行。

现在你应该有足够的信息来弄清楚为什么逆变会以它的方式运作。记住,你总是可以回到一个简单的例子,问问自己&#34;如果这是合法的,我可以在以后犯下什么错误?&#34;类型系统正在保护您免于犯这些错误!

练习:对I2<T>进行同样的分析。您是否明白为什么在两个输入位置使用T是合法的,即使它是out。 (提示:Action逆变,因此它会改变分配兼容性的方向。如果你反向指示两次会怎样?)

答案 1 :(得分:1)

所以我看到你的问题在哪里。答案应该是这样的。让我再次使用MSDN

中的示例
static object GetObject() { return null; }  
static void SetObject(object obj) { }  

static string GetString() { return ""; }  
static void SetString(string str) { }  

static void Test()  
{  
    // Covariance. A delegate specifies a return type as object,  
    // but you can assign a method that returns a string.  
    Func<object> del = GetString;  

    // Contravariance. A delegate specifies a parameter type as string,  
    // but you can assign a method that takes an object.  
    Action<string> del2 = SetObject;
    //However you can't use del2 this way, after this assingment:
    //del2(new object);
} 

这很难理解,对我而言,它是一种安静的高级抽象。

协方差

让我们仔细看看Func<object> del = GetString; 你可以做这样的事情,因为string派生自object,所以只要你得到一个返回类型的方法派生自对象你就不会有问题。想象一下,你声明了相同的del,所以你知道你会得到一个对象,所以你声明一个变量:

object returnedType = del2();

你不必关心del2是返回int还是字符串,因为它们是从对象派生的,它与以下内容相似:

 object returnedType = "string"; //Here we know what is on the left side
 //If we assign to del2 method with return type string.

逆变

现在让我们来看看Action<string> del2 = SetObject; 现在你假设你将获得一个方法字符串,所以如果有人,有一天会使用你的委托使用像SetObject(object obj)这样的方法,所以它将与之前相同:

object obj= "string"; //Here we know what is on the right

假设

它完全是关于纯多态性的。在协方差中,我们除了一个普通类型,但如果我们得到更具体的类型,它对我们没有任何改变。 相反,我们知道我们将传递什么,但是如果我们将字符串分配给字符串或字符串到对象并不重要。 (但我们不能将对象分配给字符串)。