我使用协变泛型参数扩展现有的IClonable
接口。
public interface ICloneable<out T> : ICloneable
{
new T Clone();
}
现在,我在一个基类中实现了这个接口。
public class Base : ICloneable<Base>
{
public string StrValue { get; set; }
Base ICloneable<Base>.Clone()
{
var result = (Base)FormatterServices.GetUninitializedObject(this.GetType());
result.StrValue = this.StrValue;
return result;
}
public virtual object Clone()
{
return ((ICloneable<Base>)this).Clone();
}
}
调用Clone()
按预期工作,并返回具有相同值的基类的新实例。
我创建了一个派生的Base
类,它再次实现了接口ICloneable<T>
,以返回这个新类型:
public class Sub : Base, ICloneable<Sub>
{
public int IntValue { get; set; }
Sub ICloneable<Sub>.Clone()
{
var result = (Sub)base.Clone();
result.IntValue = this.IntValue;
return result;
}
public override object Clone()
{
return ((ICloneable<Sub>)this).Clone();
}
}
但是,如果我对Clone()
实例调用Sub
,就会遇到一个StackOverflowException,因为object Base.Clone()
调用了Sub
类的Sub ICloneable<Sub>.Clone()
。
问题是协变量泛型类型参数。如果我删除了out
,那么所有操作都按预期进行。
问题是为什么((ICloneable<Base>)this).Clone()
会指向Sub.Clone()
?
协方差和反方差是否只有语法糖,编译器在派生层次结构中搜索最低可能的类型?这意味着编译器将将ICloneable<Base>
更改为ICloneable<Sub>
。
我没有找到任何官方理由来解释这种行为。
测试代码(不包括接口以及Base
和Sub
):
var b = new Sub { StrValue = "Hello World.", IntValue = 42 };
var b2 = (Base)b.Clone();
发布于 2015-09-15 12:06:44
协方差和反方差是否只有语法糖,编译器在派生层次结构中搜索最低可能的类型?
Co/反方差与句法糖无关。它允许您在编译时传递具有特定编译器限制的“较小”、更特定类型(协方差)和较大类型(反方差)。
因为您的T
参数被标记为out
,所以CLR将查看Clone
的任何重写实现的运行时。
编译时绑定是一个callvirt
到base.Clone
,这不会改变:
.method public hidebysig newslot virtual
instance object Clone () cil managed
{
// Method begins at RVA 0x2089
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance !0 class ICloneable`1<class Base>::Clone()
IL_0006: ret
} // end of method Base::Clone
运行时是多态发生的位置.
移除out
的事实恰恰强调了这一点。
https://stackoverflow.com/questions/32594179
复制