值类型接口上的协方差错误
Covariance error on Valued Type interface
我有一个通用接口,其中包含一个协变 TValue 参数和一个抽象 class,它执行一些重复性的工作以将 child classes 从该负担中解放出来。然后我有 2 个子 classes 从那个抽象的扩展而来,第一个将通用参数设置为字符串,第二个设置为 int。
这是从项目中提取的一段代码,过度简化只是为了专注于这个问题。
public interface IElement<out TValue>
{
string Name { get; }
TValue Value { get; }
}
public abstract class Element<TValue> : IElement<TValue>
{
public string Name { get; }
public TValue Value { get; set; }
public Element(string name)
{
Name = name;
}
}
public class Name : Element<string>
{
public Name() : base("Name") { }
}
public class Height : Element<int>
{
public Height() : base("Height") { }
}
基本上 - 这不是我在代码中所做的,但相当简单地说明了我遇到的问题 - 如果我尝试将 Name 分配给持有 object 的 IElement,如下所示:
IElement<object> element = new Name();
正如我所料,它成功了,因为 IElement 中的 TValue 参数是协变的。
但是,如果我将其设置为高度:
IElement<object> element = new Height();
我收到 Cannot implicitly convert type 'Height' to 'IElement<object>'. An explicit conversion exists (are you missing a cast?)
错误。
现在,我不知道为什么这适用于将通用参数设置为字符串的 class,但不适用于 int(或枚举,因为我在项目中也有一些枚举) .是因为 string 是一个 class 而 int 是一个结构吗?
非常感谢任何帮助。
很简单,因为一个是值类型。 CLR 不允许它,因为它需要保留其身份,而装箱则不允许。
Eric Lippert has a great blog about this at Representation and identity
covariant and contravariant conversions of interface and delegate
types require that all varying type arguments be of reference types.
To ensure that a variant reference conversion is always
identity-preserving, all of the conversions involving type arguments
must also be identity-preserving. The easiest way to ensure that all
the non-trivial conversions on type arguments are identity-preserving
is to restrict them to be reference conversions.
此外,您可以在各个地方的规范中阅读更多关于标识、转换、泛型和方差的信息
11.2.11 涉及类型参数的隐式转换
For a type-parameter T that is not known to be a reference type
(§15.2.5), the following conversions involving T are considered to be
boxing conversions (11.2.8) at compile-time. At run-time, if T is a
value type, the conversion is executed as a boxing conversion. At
run-time, if T is a reference type, the conversion is executed as an
implicit reference conversion or identity conversion.
我有一个通用接口,其中包含一个协变 TValue 参数和一个抽象 class,它执行一些重复性的工作以将 child classes 从该负担中解放出来。然后我有 2 个子 classes 从那个抽象的扩展而来,第一个将通用参数设置为字符串,第二个设置为 int。
这是从项目中提取的一段代码,过度简化只是为了专注于这个问题。
public interface IElement<out TValue>
{
string Name { get; }
TValue Value { get; }
}
public abstract class Element<TValue> : IElement<TValue>
{
public string Name { get; }
public TValue Value { get; set; }
public Element(string name)
{
Name = name;
}
}
public class Name : Element<string>
{
public Name() : base("Name") { }
}
public class Height : Element<int>
{
public Height() : base("Height") { }
}
基本上 - 这不是我在代码中所做的,但相当简单地说明了我遇到的问题 - 如果我尝试将 Name 分配给持有 object 的 IElement,如下所示:
IElement<object> element = new Name();
正如我所料,它成功了,因为 IElement 中的 TValue 参数是协变的。 但是,如果我将其设置为高度:
IElement<object> element = new Height();
我收到 Cannot implicitly convert type 'Height' to 'IElement<object>'. An explicit conversion exists (are you missing a cast?)
错误。
现在,我不知道为什么这适用于将通用参数设置为字符串的 class,但不适用于 int(或枚举,因为我在项目中也有一些枚举) .是因为 string 是一个 class 而 int 是一个结构吗?
非常感谢任何帮助。
很简单,因为一个是值类型。 CLR 不允许它,因为它需要保留其身份,而装箱则不允许。
Eric Lippert has a great blog about this at Representation and identity
covariant and contravariant conversions of interface and delegate types require that all varying type arguments be of reference types. To ensure that a variant reference conversion is always identity-preserving, all of the conversions involving type arguments must also be identity-preserving. The easiest way to ensure that all the non-trivial conversions on type arguments are identity-preserving is to restrict them to be reference conversions.
此外,您可以在各个地方的规范中阅读更多关于标识、转换、泛型和方差的信息
11.2.11 涉及类型参数的隐式转换
For a type-parameter T that is not known to be a reference type (§15.2.5), the following conversions involving T are considered to be boxing conversions (11.2.8) at compile-time. At run-time, if T is a value type, the conversion is executed as a boxing conversion. At run-time, if T is a reference type, the conversion is executed as an implicit reference conversion or identity conversion.