协变不是理所当然的,逆变也没有“逆”
可以看到,从”实际流程图”来看,逆变根本没有“逆”,都离不开只能安全地把子类的引用赋给父类引用这个根本。
来到这里应该基本理解逆变与协变了,不过装配脑袋的这篇文章有个更高级的问题,原文也有解答,这里我用上面画图的方式去理解它。
图解逆变与协变的相互作用
问题的提出,你知道那个正确吗?
public interface IBar<in T> { } //应该是in public interface IFoo<in T> { void Test(IBar<T> bar); } //还是out public interface IFoo<out T> { void Test(IBar<T> bar); }
答案是,如果是in的话,会编译失败,out才正确(当然不要泛型修饰符也能通过编译,但IFoo就没有协变能力了)。这里的意思就是说,一个有协 变(逆变)能力的泛型(IBar),作为另一个泛型(IFoo)的参数时,影响到了它(IFoo)的泛型的定义。乍一看以为是in的其中一个陷阱是T是在 Test方法的参数里的,所以以为是in。但这里Test的参数根本不是T,而是IBar<T>。
我们画个图来理解它。既然out可以通过,那么它的“协变流程图”应该如下:
图跟前面那些大致一样,但理解它要跟问题相反(上面问题是先定义好IBar,再去定义IFoo)。1.我们定义好一个有协变能力的IFoo,这是前 提。2.可以推出,上面的流程是成立的。3.这个流程重点是参数流向,要使整个流程成立,就必须使IBar<string> = IBar<object>成立,这不就是逆变吗?整个结论就是,有协变能力的IFoo要求它的泛型参数(IBar)有逆变能力。其实根据上面的箭头也可以理解,因为原式和变式的变向跟参数的变向是相反的,导致了它们要有相反的能力,这就是装配脑袋文章说的:方法参数的协变-反变互换原则。根据这个原理,也很容易得出,如果Test方法的返回值是IBar<T>,而不是参数,那么就要求IBar<T>要有协变能力,因为返回值的箭头与原式和变式的变向的箭头是同向的。