bob体育appc#中关于协变性和逆变性(又叫抗变)帮助理解

2019-12-10 03:44栏目:编程
TAG:

bob体育app,  后天回看了前面看的《深刻明白C#》那本书中的泛型章节,个中对泛型的可变性的接头。泛型可变性分二种:协变和逆变。逆变也又称为抗变。

那二日在看《C#高档编程(第九版)》那本书,见到了泛型接口那章。个中有关协变和逆变没太通晓,讲得某些坑爹,英特网查了重重质地,总算(以为)弄精晓了,来这里记录一下。

bob体育平台,【转】那么些年搞不懂的术语、概念:协变、逆变、不改变体

 怎么领悟这三个名词的意味:

生龙活虎、协变和逆变是如何?

先从字面上精晓 协变(Covariance卡塔尔、逆变(Contravariance卡塔尔国。
co- 是英文中表示“合营”、“同盟”的前缀。协变 的字面意思正是“与转移的动向意气风发致”。
contra- 是朝鲜语中表示“相反”的前缀,逆变 的字面意思正是是 “与转移倾向相反”。
那就是说难题来了,这里的 变化趋向 指的是如何?
C# 中对于指标(即对象引用),仅存在乎气风发种隐式类型转换,即 子类型的靶子引用到父类型的对象援用的转变。这里的变化指的正是这种 子->父 的类型转换。
object o = "hello";
//string (子类)类型的援引调换为 object (父类)类型的援引
协变与逆变即使从名字上看是四个完全相反的退换,但实则只是“子类型引用到父类型引用”那风流罗曼蒂克经过在函数中利用的 三个不等阶段 而已,接下去将详细表达那一点。

简述什么是协变性、逆变性、不改变性

  • 协变性,如:string->object (子类到父类的调换)
  • 逆变性,如:object->string (父类到子类的转变)
  • 不变性,基于上边二种景况,不可变。具体下边再做深入分析。

  ①:协变即为在泛型接口类型中央银行使out标志的连串参数。协变的字面意思是“与转换的来头相像”②逆变那正是用in来标志的泛型接口类型的档次参数。逆变的字面意思是“与变化的可行性相反”

二、使用函数的差别阶段发生的类型转变

即使有少年老成函数,选择 object 类型的参数,输出 string 类型的再次回到值:

string Method(object o)
{
    return "abc";
}

那正是说在Main函数中大家能够如此调用它:

string s = "abc";
object o = Method(s);

在乎,这里发出了若干回隐式类型调换:

  1. 在向函数输入时,参数 s 由 string 类型转换为 object 类型
  2. 在函数输出(再次来到)时,再次来到值 由 string 类型转变为 object 类型
    我们那边能够看做是函数签字可产生调换(无论函数的剧情,不影响结果):
  3. string Method(object o卡塔尔(قطر‎ 可转换为 string Method(string o卡塔尔
  4. string Method(string o卡塔尔(قطر‎ 可转变为 object Method(string o卡塔尔国
    也正是说,在函数输入时,函数的 输入类型 可由 object 转换为 string,父->子
    在函数输出时,函数的 输出类型 可由string转变为object,子->父

泛型委托的可变性

先选择框架定义的泛型委托Func和Action做例子(不领悟的请戳)

协变:(string->object)

Func<string> func1 = () => "农码一生";
Func<object> func2 = func1;

逆变:(object->string)

Action<object> func3 = t => { };
Action<string> func4 = func3;

地点代码未有其余难题。

随着大家温馨定义委托试试:

bob体育平台 1

笔者X,看人不来哦。为何自定义的寄托却不能够协变呢。

自小编看看系统定义的Func到底和我们自定义的有怎么样不一致:

public delegate TResult Func<out TResult>();

多了叁个out,什么鬼:

  • out:对于泛型类型参数,out 关键字钦命该品种参数是协变的。 能够在泛型接口和寄托中选拔 out 关键字。(来源)
  • in:对于泛型类型参数,in 关键字钦命该品种参数是逆变的。 可以在泛型接口和嘱托中央银行使 in 关键字。(来源)

那么我们得以修改自定义委托:

bob体育平台 2

完美!

那借使大家要兑现逆变性呢:

bob体育平台 3

间接逆变是不可行的,大家须求改革泛型类型参数:

bob体育平台 4

笔者们发现整个信托参数都变了。本来的再次来到值,改成输入参数才行。

结论:

  • in->输入参数->可逆变(父类到子类的调换[如 object->string])
  • out->再次来到值->可协变(子类到父类的变通[如 string->object])

 

即使:假诺泛型参数中既存在in又存在out改如何:

delegate Tout MyFunc<in Tin, out Tout>(Tin obj);

MyFunc<object, string> str1 = t => "农码一生";
MyFunc<string, string> str2 = str1;//第一个泛型的逆变(object->string)
MyFunc<object, object> str3 = str1;//第二个泛型的协变(string->object)
MyFunc<string, object> str4 = str1;//第一个泛型的逆变和第二个泛型的协变

以上都是未曾难题的。 

下一场大家看看编译后的C#代码:

bob体育平台 5

结论:

  • 所谓的逆变其实只是编写翻译后实行了威迫类型转变而已。

以上代码也可以平素写成:

//delegate Tout MyFunc<in Tin, out Tout>(Tin obj);
MyFunc<string, string> str5 = t => "农码一生";
MyFunc<object, object> str6 = t => "农码一生";
MyFunc<string, object> str7 = t => "农码一生";

  须要注意的是不管协变照旧逆变也只好在泛型接口中来行使。

三、驾驭泛型接口中的 in、out参数

未有一点点名in、out的场馆
倘使有少年老成泛型接口,并且有一个类完成了此接口:

interface IDemo<T>
{
    T Method(T value);
}
public class Demo : IDemo<string>
{
    //实现接口 IDemo<string>
    public string Method(string value)
    {
        return value;
    }
}

在Main函数中如此写:
IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;
地点的这段代码中的第二行李包裹涵了一个万意气风发:
I德姆o<string> 类型能够隐式转换为 I德姆o<object> 类型
那乍看上去犹如“子类型援引调换为父类型援用” 一样,然则很可惜,他们并不相通。假设能够张开隐式类型转变,那就代表:
string Method(string value卡塔尔 能转换为 object Method(object value卡塔尔国
从上生龙活虎节中我们精通,在函数那输入和输出阶段,其项目可转移方向是例外的。所以在C#中,要想选用泛型接口类型的隐式转换,要求商讨“输入”和“输出”两种状态。
接口仅用于出口的情事,协变

interface IDemo<out T>
{
    //仅将类型 T 用于输出
    T Method(object value);
}
public class Demo : IDemo<string>
{
    //实现接口
    public string Method (object value)
    {
        //别忘了类型转换!
        return value.ToString();
    }
} 

在Main函数中如此写:
IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;
可将 string Method (object value) 转换为 object Method (object value)
就可以将 I德姆o<string> 类型转变为 I德姆o<object> 类型。
仅从泛型的连串上看,那是 “子->父” 的转换,与第少年老成节中涉嫌的转变方向相似,称之为“协变”。
接口仅用于输入的事态,逆变
同理大家能够给 T 加上 in 参数:

interface IDemo<in T>
{
    //仅将类型 T 用于输入
    string Method(T value);
}
public class Demo : IDemo<object>
{
    //实现接口
    public string Method (object value)
    {
        return value.ToString();
    }
} 

在Main函数中如此写:
IDemo<object> demoObj = new Demo();
IDemo<string> demoStr = demoObj;
此间可将 string Method (object value卡塔尔国 转换为 string Method (string value卡塔尔(قطر‎
就可以将 IDemo<object> 类型调换为 I德姆o<string> 类型。
仅从泛型的门类上看,那是 “父->子” 的转变,与第风华正茂节中提到的转变方向相反,称之为“逆变”,不经常也译作“抗变”或“反变”。

泛型接口的可变性

随之看框架暗中认可接口:

协变:(子类->父类)

IEnumerable<string> list = new List<string>();
IEnumerable<object> list2 = list;

逆变:(父类-> 子类)

IComparable<object> list3 = null;
IComparable<string> list4 = list3;

接下去我们尝试自定泛型接口:

第一定义测验项目、接口:

// 人
public class People
{ }
//老师(继承People[人])
public class Teacher : People
{ }
//运动
public interface IMotion<T>
{ }
//跑步
public class Run<T> : IMotion<T>
{ }

然后大家测量检验协变性:

bob体育平台 6

平等大家须要把接口 interface IMotion<T> 定义为 interface IMotion<out T> 

//运动
public interface IMotion<out T>{}

IMotion<Teacher> x = new Run<Teacher>();
IMotion<People> y = x;

要是大家要测验逆变性,则须求把 interface IMotion<T>  定义为 interface IMotion<in T> 

//运动
public interface IMotion<in T>{}

IMotion<People> x2 = new Run<People>();
IMotion<Teacher> y2 = x2;

泛型接口的逆变,编写翻译后生龙活虎致进行了挟持转变:

bob体育平台 7

理当如此,大家也足以间接写成:

IMotion<Teacher> y3 = new Run<People>();

  先来举个为主的例证,来抓好你对可变性的理解。在C#中有隐式类型调换,举例:

四、总结

以上只谈谈了协变与逆变在措施中的情状,其实在品质中状态也邻相仿,不再表达。
或是大家也意识了,所谓“协”与“逆”都以只是生机勃勃种表象,其内在精气神儿为相符进度。
“协变”与“逆变”中的“协”与“逆”表示泛型接口在将品种参数仅用于输入或输出的事态下,其连串参数的隐式调换所坚决守护的规律。

不变性

从上边大家精晓逆变性的代码编写翻译后都会进展强迫调换。假设:那大家不用out、in直接手动免强转变是不是足以?:

// 人
public class People { }
//老师(继承People[人])
public class Teacher : People { }
//运动
public interface IMotion<T> { }
//跑步
public class Run<T> : IMotion<T> { }

//协变
IMotion<Teacher> x = new Run<Teacher>();
IMotion<People> y = (IMotion<People>)x;

//逆变
IMotion<People> x2 = new Run<People>();
IMotion<Teacher> y2 = (IMotion<Teacher>)x2;
IMotion<Teacher> y3 = (IMotion<Teacher>)new Run<People>();

天才的自个儿发觉编写翻译成功了,未有任何难题!且还能何况协变、逆变??不对,真的天才了呢?大家运营试试:

bob体育平台 8

bob体育平台 9

简单的说作者要么太单纯了,借使的确这么轻便绕过去,Microsoft又何苦去搞个out、in关键字。

对于同叁个泛型参数,大家既想有协变性又想逆变性,如何做?答案是不可行。这就能够产出第三种情形,既不得以协变又不可能逆变。称为不改变性。

如(大家在IMotion定义七个办法):

//运动
public interface IMotion<T>
{
    T Show();
    void Match(T t);
}

地点大家测量检验过,代码直接压迫转变是不可能贯彻协变、逆变的。那么大家不能不通过out、in来兑现。如若未来我们在泛型参数增添out或in属性会怎么?:

bob体育平台 10

bob体育平台 11

大家开掘out和in都不能够用。在用out时,有个传入参数为泛型 void Match(T t卡塔尔国 的点子。使用in时,有个再次来到参数为泛型 T Show(卡塔尔国 的诀窍。今后就现身了是矛更尖锐抑或盾更坚硬的标题了。

末段结果是:都无法用,既不能协变,也无法逆变。此为不变体

小知识:

C#4.0事前 IEnumerable<T> 、 IComparable<T> 、 IQueryable<T> 等接口都不帮忙可变性,在4.0及将来才支撑。因为4.0事情发生在此之前定义的泛型接口未有加多out、in关键字,有意思味能够切换版本看看。

  string str = "nibian";
  object str1 = str;
  Console.WriteLine(str1);
协变

当泛型接口类型仅用于出口(使用重要词 out),其种类参数隐式转换所根据的准绳与对象援用的类型转变规律雷同,称之为“协变”

拉开思忖

为什么in[输入参数]就不能不逆变?拆解解析如下:

// 人
public class People { }
//老师(继承People[人])
public class Teacher : People
{
    //薪水
    public decimal Salary { get; set; }
}

//运动
public interface IMotion<in T>
{
    void Match(T t);
}
//跑步
public class Run<T> : IMotion<T>
{
    public void Match(T t)
    {
        //假设中间有很多逻辑.....       
    }
}

bob体育平台 12

为什么out[返回值]只能协变?浅析如下:

// 人
public class People { }
//老师(继承People[人])
public class Teacher : People
{
    //薪水
    public decimal Salary { get; set; }
}

//运动
public interface IMotion<out T>
{
    T Show();
    //void Match(T t);
}
//跑步
public class Run<T> : IMotion<T>
{
    public T Show()
    {
        return default(T);
    }
    //public void Match(T t)
    //{
    //    //假设中间有很多逻辑.....         
    //}
}

bob体育平台 13

此处有四个关键点:

  • 盛传参数(in)是把参数当成父类来用,显明能够逆变(子类当成父类来用[里氏替换原则]),不过却不得以把父类当子类来用(如:子类存在有而父类未有的法子或性质)。
  • 再次来到值(out)重返值类型用父类来接收,鲜明能够协变(父类能够摄取一切子类),但却不可用子类采纳父类数据(如:父类代表的对象无法强迫转给子类[string str = (string)objcet])。

。。。是否有一些越想越迷糊,想不知道就逐步想。本身动动手。

后生可畏经实际想的头大,就把它正是是水龟的臀部(龟腚规定)吧,知道是C#做的黄金时代种安全范围!

版权声明:本文由bob体育app发布于编程,转载请注明出处:bob体育appc#中关于协变性和逆变性(又叫抗变)帮助理解