StackOverflowException 通过使用具有协变泛型参数的显式接口实现
StackOverflowException by using explict interface implementation having covariant generic parameter
我正在使用协变泛型参数扩展现有的 IClonable
接口。
public interface ICloneable<out T> : ICloneable
{
new T Clone();
}
现在我在基础class中实现了这个接口。
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()
按预期工作并且 return 是具有相同值的基础 class 的新实例。
我创建了 Base
的派生 class,它再次实现接口 ICloneable<T>
到 return 这个新类型:
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();
}
}
但是如果我在 Sub
的实例上调用 Clone()
我 运行 进入 WhosebugException 因为 object Base.Clone()
调用 class 的 Sub ICloneable<Sub>.Clone()
Sub
.
问题出在协变泛型类型参数上。如果我删除 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();
Is co- and contravariance only syntactic sugar and the compiler searches for the lowest possible type in the derivation hierarchy?
Co/contravariance 与语法糖无关。它允许您在编译时传递 "smaller"、更具体的类型(协变)和具有特定编译器限制的更大的类型(逆变)。
因为您的 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
运行-时间是polymorphism happens.
移除 out
调用基数这一事实恰恰强调了这一点。
我正在使用协变泛型参数扩展现有的 IClonable
接口。
public interface ICloneable<out T> : ICloneable
{
new T Clone();
}
现在我在基础class中实现了这个接口。
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()
按预期工作并且 return 是具有相同值的基础 class 的新实例。
我创建了 Base
的派生 class,它再次实现接口 ICloneable<T>
到 return 这个新类型:
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();
}
}
但是如果我在 Sub
的实例上调用 Clone()
我 运行 进入 WhosebugException 因为 object Base.Clone()
调用 class 的 Sub ICloneable<Sub>.Clone()
Sub
.
问题出在协变泛型类型参数上。如果我删除 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();
Is co- and contravariance only syntactic sugar and the compiler searches for the lowest possible type in the derivation hierarchy?
Co/contravariance 与语法糖无关。它允许您在编译时传递 "smaller"、更具体的类型(协变)和具有特定编译器限制的更大的类型(逆变)。
因为您的 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
运行-时间是polymorphism happens.
移除 out
调用基数这一事实恰恰强调了这一点。