2015-09-15 6 views
3

Я расширяю существующий интерфейс IClonable, используя ковариантный общий аргумент.StackOverflowException с использованием реализации эксклюзионного интерфейса с ковариантным общим параметром

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 ICloneable<Sub>.Clone() из класс Sub.

Проблема заключается в ковариантном параметре общего типа. Если я удалю out, все будет работать должным образом.

Вопрос в том, почему ((ICloneable<Base>)this).Clone() указывает на Sub.Clone()?

Сочетанный и контравариантный синтаксический сахар, и компилятор ищет наименьший возможный тип в иерархии деривации? Это означает, что ICloneable<Base> будет изменен на ICloneable<Sub> компилятором.

Я не нашел официального объяснения, объясняющего это поведение.

Testcode (исключая интерфейс и Base и Sub):

var b = new Sub { StrValue = "Hello World.", IntValue = 42 }; 
var b2 = (Base)b.Clone(); 

ответ

5

Является СО- и контрвариация только синтаксический сахар и компилятор ищет минимально возможный тип в иерархии вывода?

Co/contravвариантность не имеет ничего общего с синтаксическим сахаром. Он позволяет вам передавать «меньшие», более конкретные типы (ковариация) и более крупные (контравариантность) с конкретными ограничениями компилятора во время компиляции.

Поскольку ваш параметр 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 

Run-времени, где polymorphism happens.

Тот факт, что удаление out вызывает базу, подчеркивает именно это.

+0

_ CLR будет искать время выполнения для любой переопределенной реализации 'Clone'_ - возможно, я не понял вас правильно, но нет переопределенной реализации' ICloneable .Clone() '.Единственное переопределение «Clone» - это нелогичный «IClonable.Clone()». Возможно, вы имеете в виду, что CLR ищет более специфический тип во время выполнения, находит его и меняет вызов на более конкретный тип. Это нормально. Есть ли способ предотвратить выполнение CLR в этом конкретном случае? –

+1

@ Vera rind Поскольку 'ICloneable ' является ковариантным, когда вы вызываете '((ICloneable ) this) .Clone();', литье 'this' соответствует' ICloneable ', а не 'ICloneable '. –

+0

Да, я понимаю. но последний вопрос все еще открыт: есть ли способ предотвратить выполнение CLR в этом конкретном случае? –

Смежные вопросы