一个bug
这是两个泛型列表。你不用在意他是什么,你只要知道他和数组很像就行。
List<string> strList = new List<string>() { "123", "456", "789" };
List<object> objList = new List<object>(3);
现在把第二个列表的内容,用第一个列表的内容覆写。
for (int i = 0; i < strList.Count&&i<objList.Count; i++)
{
objList[i]= strList[i];
}
把一个值赋值给不同类型的变量。但因为变量类型和值的类型有继承关系,所以可以成立。
那么既然string类型可以赋值给object类型,那你有没有想过不访问元素,直接从外面进行赋值。
像这样:
List<string> strList = new List<string>() { "123", "456", "789" };
List<object> objList = strList;
首先告诉你,这种做法在数组上是可以的,但是按照c#的类型安全理念,不应该成立。
因为会发生以下错误:
string[] strArr = new string[] { "123", "456", "789" };
object[] objArr = strArr;
objArr[0] = 12;
这在编译时是可以通过的,但实际运行起来,会出现类型转换失败的异常。
协变逆变
泛型具有严格的限制,像上面那种使用方式是不允许的。
而协变逆变就是用来解开这种限制。
而协变逆变也有限制,不能对类使用,只能对接口或委托使用。
(因为他们不会储存字段,只有方法)。
协变
协变:和谐的变化,儿子随着时间流逝会当父亲。泛型填的是子类,可以迎合父类。
使用out关键字修饰泛型占位符,表示输出。修饰的泛型占位符仅能作为返回类型。
interface IOut<out T>
{
T GetValue();
}
class COut : IOut<string>
{
public string GetValue() => "hello";
}
IOut<string> out1 = new COut();
IOut<object> out2 = out1;
因为这个接口中只有输出的泛型。
在输出的时候把string作为object看待是没有问题的。
逆变
逆变:大逆不道的变化,要父亲当儿子。泛型填的是父类,却要迎合子类。
使用in关键字修饰泛型占位符,表示输入。修饰的泛型占位符仅能作为参数类型。
interface IIn<in T> {
void SetValue(T value);
}
class CIn : IIn<object>
{
public void SetValue(object value) { }
}
IIn<object>in1=new CIn();
IIn<string> in2 = in1;
因为这个接口中只有参数的泛型,
在作为参数的时候,把string作为object看待是没有问题的。
而泛型类中的字段,是技能作为输入,又能作为输出的存在。
所以协变逆变对泛型类不可用。
协变逆变不能把泛型参数装箱拆箱
所有的值类型不能参与协变逆变。
class CT<T> : IOut<T>, IIn<T>
{
T IOut<T>.GetValue() => throw new NotImplementedException();
void IIn<T>.SetValue(T value) => throw new NotImplementedException();
}
CT<Stream> ct = new CT<Stream>();
IIn<FileStream> Iin = ct;
IOut<object> Iout = ct;
Iout = new CT<int>();
IIn<int> Iin2 = new CT<object>();