Generic variance 泛型型变
「It’s about safely converting between generic types based on their type arguments and paying particular attention to the direction in which data travels.」
只有接口和委派能型变,实施型变接口的类依然是Invariance。
IEnumerable<string> strings = new List<string>{"a","b","c"};
IEnumerable<object> objects = strings;
有效
IList<string> strings = new List<string> { "a", "b", "c" };
IList<object> objects = strings;
无效
因为IEnumable 仅有输出方法,而IList同时具有输入输出方法,输入时,可能会引发错误。
IList<string> strings = new List<string> { "a", "b", "c" };
IList<object> objects = strings;
objects.Add(new object());
string element = strings[3];
objects其实是引用了strings堆地址,对于objects的修改同时修改了strings,所以在读取string[3]时发生类型错误。
注意:第二句在编译时会报错,与其在运行时报错,不如在编译时提前将错误暴露。
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction;
stringAction("Print me");
仅作为输入
总结:
- Covariance(协变) occurs when values are returned only as output. <out T>
- Contravariance(逆变) occurs when values are accepted only as input. <in T>
- Invariance(不变) occurs when values are used as input and output
型变只适用于identity and implicit reference conversion ,不支持 boxing and numeric conversion
举个例子:from Func<object ,int> to Func<string ,long>
第一个类型参数是逆变,从string到object是implicit reference conversion ,因此可以通过。为什么是string到object,因为第一个类型参数是入参,我们要考虑的是在使用 Func<string ,long>赋值,Func<object ,int> 的内部逻辑在使用 Func<string ,long>的入参时能否正常运行。即object能做的事string是否都能做。
第二个类型参数是协变,从int到long是numeric conversion,因此无法通过。因为第二个类型参数是出参,我们要考虑的是Func<object ,int>的出参能否转换为 Func<string ,long>的出参。
为什么值类型无法型变?查了一下微软官方文档只找到了「Variance in generic interfaces is supported for reference types only. Value types do not support variance. For example, IEnumerable<int>
cannot be implicitly converted to IEnumerable<object>
, because integers are represented by a value type.」和「Variance for generic type parameters is supported for reference types only. For example, DVariant<int>
can’t be implicitly converted to DVariant<Object>
or DVariant<long>
, because integer is a value type.」
此为书中应用型变例子,已补全。
static void Main(string[] args)
{
List<Circle> circles = new List<Circle>()
{
new(5.3),
new(2),
new(10.5),
};
circles.Sort(new AreaComparer());//Circle自动逆变为Shape
foreach (var circle in circles)
{
Console.WriteLine(circle.Area);
}
}
public class Shape
{
public double Area { get; set; }
}
public class AreaComparer : IComparer<Shape>
{
int IComparer<Shape>.Compare(Shape? x, Shape? y)
{
x = x ?? new() { Area = 0 };
y = y ?? new() { Area = 0 };
if (x.Area == y.Area) return 0;
return x.Area > y.Area ? 1 : -1;
}
}
public class Circle : Shape
{
public Circle(double area)
{
base.Area = area;
}
}