我正在使用协变泛型参数扩展现有的
现在我在一个基类中实现了这个接口。
但是,如果我在一个
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();
}
}
但是,如果我在一个
Sub
实例上调用Clone()
,我会遇到堆栈溢出异常,因为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();
Clone
的任何重写实现。也许我没有正确理解您的意思,但是ICloneable<T>.Clone()
没有重写实现。唯一的Clone
重写是非泛型的IClonable.Clone()
。也许您的意思是CLR在运行时查找更具体的类型,找到它并将调用更改为更具体的类型。这没问题。在这种特殊情况下,有没有办法阻止CLR这样做? - Sebastian SchumannICloneable<out T>
是协变的,所以当你调用((ICloneable<Base>)this).Clone();
时,对this
的转换匹配了ICloneable<Sub>
而不是ICloneable<Base>
。 - Yuval ItzchakovICloneable
而不是直接实现你的泛型接口呢? - Yuval ItzchakovIClonable
。所有其他知道类型的部分都使用ICloneable<T>
。 - Sebastian Schumann