首页 > 解决方案 > 值类型接口上的协方差错误

问题描述

我有一个通用接口,其中包含一个协变 TValue 参数和一个抽象类,该类执行一些重复的工作以将子类从该负担中解放出来。然后我有 2 个从该抽象类扩展的子类,第一个将泛型参数设置为字符串,第二个设置为 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 分配给这样的 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?)错误。

现在,我不知道为什么这适用于将泛型参数设置为字符串的类,但不适用于 int(或枚举,因为我在项目中也有一些枚举)。是因为 string 是一个类而 int 是一个结构吗?

任何帮助是极大的赞赏。

标签: c#genericstype-conversioncovariancevalue-type

解决方案


简单地说,因为 one 是值类型。CLR 不允许它,因为它需要保留其身份,而装箱则不需要。

Eric Lippert在Representation and identity上有一个很棒的博客

接口和委托类型的协变和逆变转换要求所有不同类型的参数都是引用类型。为确保变体引用转换始终保持身份,所有涉及类型参数的转换也必须保持身份。确保类型参数上的所有重要转换都保持身份的最简单方法是将它们限制为引用转换。

此外,您可以在各个地方的规范中阅读更多关于身份、转换、泛型和方差的信息

11.2.11 涉及类型参数的隐式转换

对于未知的类型参数 T 是引用类型(第 15.2.5 节),以下涉及 T 的转换被认为是编译时的装箱转换(11.2.8)。在运行时,如果 T 是值类型,则转换作为装箱转换执行。在运行时,如果 T 是引用类型,则转换将作为隐式引用转换或标识转换执行。


推荐阅读