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>

我没有找到任何官方理由来解释这种行为。

测试代码(不包括接口和BaseSub):

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 的任何重写实现。

编译时绑定是 callvirtbase.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 调用基数这一事实恰恰强调了这一点。