18.协变逆变
18.1 知识点
协变逆变
协变:和谐的变化,自然的变化。因为里氏替换原则,父类可以装子类,所以子类变父类。比如 string
变成 object
,感受是和谐的。
逆变:逆常规的变化,不正常的变化。因为里氏替换原则,父类可以装子类,但是子类不能装父类,所以父类变子类。比如 object
变成 string
,感受是不和谐的。
协变和逆变是用来修饰泛型的。
- 协变:
out
- 逆变:
in
用于在泛型中修饰泛型字母,只有泛型接口和泛型委托能使用。
协变逆变的作用
class语句块外 namespace语句块内
返回值和参数:
用
out
修饰的泛型委托只能作为返回值,不能作为参数。delegate T TestOut1<out T>();//out修饰了T T只能作为返回值 //delegate T TestOut2<out T>(T t);//报错 out修饰了T T不能作为参数
用
in
修饰的泛型委托只能作为参数,不能作为返回值。delegate void TestIn1<in T>(T t);//in修饰了T T只能作为参数 //delegate T TestIn2<in T>(T t);//报错 in修饰了T T不能作为返回值
用
out
修饰的泛型接口中,泛型类型只能作为返回值。interface ITestOut<out T> { T TestFun1();//out修饰了T T只能作为返回值 //void TestFun2(T t);//报错 out修饰了T T不能作为参数 }
用
in
修饰的泛型接口中,泛型类型只能作为参数。interface ITestIn<in T> { //T TestFun1();//报错 in修饰了T T不能作为返回值 void TestFun2(T t);//in修饰了T T只能作为参数 }
只有泛型接口和泛型委托能使用,泛型类、结构体等会报错。
//class Test<in T>//报错 //{ //}
结合里氏替换原则理解:
// 定义父子类例子
// 父类
class Father
{
}
// 子类
class Son : Father
{
}
主函数内
协变:父类总是能被子类替换。
// 声明一个out修饰的泛型Son委托变量,装载一个返回Son的匿名函数
TestOut1<Son> outSon = () =>
{
return new Son();
};
// 再声明一个out修饰的泛型Father委托变量
// outFather委托变量可以装载outSon委托变量
TestOut1<Father> outFather = outSon;
Father father = outFather();
// 实际上,执行outFather 返回的是 outSon里面装的函数 返回的是Son
// Father类型的变量father 是用 父类Father 装 子类Son
逆变:父类总是能被子类替换。
// 声明一个in修饰的泛型Father委托变量,装载一个参数是Father的匿名函数
TestIn1<Father> inFather = (value) =>
{
};
// 再声明一个in修饰的泛型Son委托变量
// inSon委托变量可以装载inFather委托变量
TestIn1<Son> inSon = inFather;
inSon(new Son());
// 实际上,执行inSon时,实际执行的是inFather
// 执行inSon时,传入Son类型的参数
// 因为实际执行的是inFather
// 正常来说执行inFather时,要传入的是Father类型的参数
// 但是现在传入的是Son类型的参数
// 其实还是 参数类型Father 装 参数类型Son,父类装子类
总结
- 协变:
out
- 逆变:
in
- 用来修饰泛型替代符,只能修饰接口和委托中的泛型。
作用:
out
修饰的泛型类型只能作为返回值类型,in
修饰的泛型类型只能作为参数类型。- 遵循里氏替换原则的,用
out
和in
修饰的泛型委托可以相互装载(有父子关系的泛型)。协变:父类泛型委托装子类泛型委托
逆变:子类泛型委托装父类泛型委托
18.2 知识点代码
using System;
namespace Lesson17_协变逆变
{
#region 知识点一 什么是协变逆变
//协变:和谐的变化,自然的变化
//因为 里氏替换原则 父类可以装子类
//所以 子类变父类
//比如 string 变成 object
//感受是和谐的
//逆变:逆常规的变化,不正常的变化
//因为 里氏替换原则 父类可以装子类 但是子类不能装父类
//所以 父类变子类
//比如 object 变成 string
//感受是不和谐的
//协变和逆变是用来修饰泛型的
//协变:out
//逆变:in
//用于在泛型中 修饰 泛型字母的
//只有泛型接口和泛型委托能使用
#endregion
//class语句块外 namespace语句块内
#region 知识点二 协变逆变的作用
//1.返回值 和 参数
//用out修饰的泛型委托 只能作为返回值 不能作为参数
delegate T TestOut1<out T>();//out修饰了T T只能作为返回值
//delegate T TestOut2<out T>(T t);//报错 out修饰了T T不能作为参数
//用in修饰的泛型委托 只能作为参数 不能作为返回值
delegate void TestIn1<in T>(T t);//in修饰了T T只能作为参数
//delegate T TestIn2<in T>(T t);//报错 in修饰了T T不能作为返回值
//用out修饰的泛型接口
interface ITestOut<out T>
{
T TestFun1();//out修饰了T T只能作为返回值
//void TestFun2(T t);//报错 out修饰了T T不能作为参数
}
//用in修饰的泛型接口
interface ITestIn<in T>
{
//T TestFun1();//报错 in修饰了T T不能作为返回值
void TestFun2(T t);//in修饰了T T只能作为参数
}
//只有泛型接口和泛型委托能使用 泛型类 结构体等 会报错
//class Test<in T>//报错
//{
//}
//2.结合里氏替换原则理解
//父类
class Father
{
}
//子类
class Son : Father
{
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("协变逆变");
//主函数内
#region 知识点二 协变逆变的作用(结合里氏替换原则理解)
//协变 父类总是能被子类替换
//看起来 就是 son ——> father
//声明一个out修饰的泛型Son委托变量 装载一个返回Son的匿名函数
TestOut1<Son> outSon = () =>
{
return new Son();
};
//再声明一个out修饰的泛型Father委托变量
//正常来说 假如TestOut1不是out修饰的泛型委托的话
//outFather委托变量 不能装 outSon委托变量 会报错
//但是 TestOut1加了out修饰的泛型 可以用 outFather 装 outSon
TestOut1<Father> outFather = outSon;
Father father = outFather();
//实际上 执行outFather 返回的是 outSon里面装的函数 返回的是Son
//Father类型的变量father 是用 父类Father 装 子类Son
//逆变 父类总是能被子类替换
//看起来像是 father——>son 明明是传父类 但是你传子类 不和谐的
//声明一个in修饰的泛型Father委托变量 装载一个参数是Father的匿名函数
TestIn1<Father> inFather = (value) =>
{
};
//再声明一个in修饰的泛型Son委托变量
//正常来说 假如TestIn1不是in修饰的泛型委托的话
//inSon委托变量 不能装 inFather委托变量 会报错
//但是 TestIn1加了in修饰的泛型 可以用 inSon 装 inFather
TestIn1<Son> inSon = inFather;
inSon(new Son());
//实际上 执行inSon时 实际执行的是 inFather
//执行inSon时 传入Son类型的参数
//因为实际执行的是inFather
//正常来说执行inFather时 要传入的是Father类型的参数
//但是现在 传入的是Son类型的参数
//其实还是 参数类型Father 装 参数类型Son 父类装子类
#endregion
}
}
//总结
//协变 out
//逆变 in
//用来修饰 泛型替代符的 只能修饰接口和委托中的泛型
//作用
//1.out修饰的泛型类型 只能作为返回值类型 in修饰的泛型类型 只能作为 参数类型
//2.遵循里氏替换原则的 用out和in修饰的 泛型委托 可以相互装载(有父子关系的泛型)
// 协变 父类泛型委托装子类泛型委托 逆变 子类泛型委托装父类泛型委托
}
18.3 练习题
请描述协变逆变有什么作用
- 协变和逆变是用来修饰泛型替代符的,在泛型委托和泛型接口中起作用。
- 使用
out
修饰的泛型类型只能作为返回值类型,称为协变;而使用in
修饰的泛型类型只能作为参数类型,称为逆变。 - 遵循里氏替换原则,使用
out
和in
修饰的泛型委托如果类型是父子关系,则可以相互装载:- 协变:父类泛型委托容器可以装载子类泛型委托容器。
- 逆变:子类泛型委托容器可以装载父类泛型委托容器。
通过代码说明协变和逆变的作用
class语句块外 namespace语句块内
// 协变
delegate T TestOut<out T>();
// 逆变
delegate void TestIn<in T>(T v);
class Father
{
}
class Son : Father
{
}
主函数内
// 协变 代码
TestOut<Son> testOutSon = () =>
{
return new Son();
};
TestOut<Father> testOutFather = testOutSon;
Father father = testOutFather();
// 逆变 代码
TestIn<Father> testInFather = (value) =>
{
};
TestIn<Son> testInSon = testInFather;
testInSon(new Son());
18.4 练习题代码
using System;
namespace Lesson17_练习题
{
//class语句块外 namespace语句块内
#region 练习题二
//协变
delegate T TestOut<out T>();
//逆变
delegate void TestIn<in T>(T v);
class Father
{
}
class Son : Father
{
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("协变逆变练习题");
#region 练习题一
//请描述协变逆变有什么作用
//是用来修饰泛型替代符的 泛型委托和泛型接口中
//1.out修饰的泛型类型 只能作为返回值类型 协变
// in修饰的泛型类型 只能作为参数类型 逆变
//2.遵循里氏替换原则 用out和in修饰的泛型委托 如果类型是父子关系 那么可以相互装载
// 协变: 父类泛型委托容器可以装 子类泛型委托容器
// 逆变: 子类泛型委托容器可以装 父类泛型委托容器
#endregion
//主函数内
#region 练习题二
//通过代码说明协变和逆变的作用
//协变 代码
TestOut<Son> testOutSon = () =>
{
return new Son();
};
TestOut<Father> testOutFather = testOutSon;
Father father = testOutFather();
//逆变 代码
TestIn<Father> testInFather = (value) =>
{
};
TestIn<Son> testInSon = testInFather;
testInSon(new Son());
#endregion
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com