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");

仅作为输入

总结:

  1. Covariance(协变) occurs when values are returned only as output. <out T>
  2. Contravariance(逆变) occurs when values are accepted only as input. <in T>
  3. 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;
    }
}