简体   繁体   中英

StackOverflowException by using explict interface implementation having covariant generic parameter

I'm extending the exisiting IClonable interface using a covariant generic argument.

public interface ICloneable<out T> : ICloneable
{
    new T Clone();
}

Now I implemented this interface in a base 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();
    }
}

Calling Clone() works as expected and returns a new instance of base class having the same values.

I created a derived class of Base that implements the interface ICloneable<T> again to return this new type:

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();
    }
}

But if I call Clone() on an instance of Sub I run into a StackOverflowException because object Base.Clone() calls Sub ICloneable<Sub>.Clone() of class Sub .

The problem is the covariant generic type parameter. If I remove out all works as expected.

The question is why does ((ICloneable<Base>)this).Clone() point to Sub.Clone() ?

Is co- and contravariance only syntactic sugar and the compiler searches for the lowest possible type in the derivation hierarchy? That means that ICloneable<Base> will be changed to ICloneable<Sub> by the compiler.

I didn't find any official justification that explains this behaviour.

Testcode (excluding the interface and Base and 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 has nothing to do with syntactical sugar. It allows you to pass "smaller", more specific types (covariance) and larger ones (contravariance) with specific compiler restrictions, at compile time.

Because your T parameter is marked as out , the CLR will look at run-time for any override implementation of Clone .

The compile time binding is a callvirt to base.Clone , that doesn't change:

.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

Run-time is where the polymorphism happens .

The fact that removing out calls the base emphasises that exactly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM