为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
慕标5832272 2021-08-07 16:34:04
为什么像IEnumerable<out T>type这样的协变类型参数T只用于返回类型(只读)或像Action<in T>type这样的逆逆变类型参数T只用作参数类型(只写)?换句话说,我认为纯协变概念和仅用于成员返回类型的 c# 协变类型参数之间存在关系。
查看完整描述

2 回答

?
互换的青春

TA贡献1797条经验 获得超6个赞

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


第一关:T不具备返回类型仅被使用。例如:


interface I1<out T>{ T M1(); }

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

在I1<T>,T仅用于返回类型的位置。但是 in I2,T用在一个 input 中a, anI1<T>是 the 的输入Action,所以从某种意义上说,它在这里被用在两个输入位置。


但让我们考虑一个更简单的情况。为什么我们可以使I1in 协变T但不能使in逆变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返回什么,它总是 a Mammal,因此总是 an Animal。


但这不可能是合法的:


I1<Giraffe> i1g = i1m; // Not legal

Giraffe g = i1g.M1(); // Returns a tiger; assigned to giraffe, bad!

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


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


练习:对I2<T>. 你明白为什么T在两个输入位置使用是合法的,即使它是out. (提示:Action是逆变的,所以它反转了赋值兼容性的方向。如果你反转方向两次会发生什么?)


查看完整回答
反对 回复 2021-08-07
?
森林海

TA贡献2011条经验 获得超2个赞

所以我知道你的问题在哪里。答案应该是这样。让我再次使用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; 你被允许做这样的事情,因为字符串派生自对象,所以只要你得到一个返回类型派生自对象的方法,你就没有问题。想象一下,您声明了相同的 del,因此您所知道的将获得对象,因此您声明了一个变量:


object returnedType = del2();

您不必关心 del2 是返回 int 还是 string,因为它们派生自 object 它将类似于:


 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

假设

这都是关于纯多态性的。在协方差中,我们排除了一种通用类型,但如果我们获得更具体的类型,它不会对我们产生任何改变。逆变我们知道我们将传递什么,但我们是否将字符串分配给字符串或字符串分配给对象并不重要。(但我们不能将对象分配给字符串)。


查看完整回答
反对 回复 2021-08-07
  • 2 回答
  • 0 关注
  • 176 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信