为什么通用代码编译而不约束T?

时间:2016-04-13 15:13:10

标签: c# generics

编程C#5.0 中的示例10-11(p.343)中提取以下代码段:

public static T[] Select<T>(this CultureInfo[] cultures,
                            Func<CultureInfo, T> map)
{
    var result = new T[cultures.Length];
    for (int i = 0; i < cultures.Length; ++i)
    {
        result[i] = map(cultures[i]);
    }
    return result;
}

我无法弄清楚如何在不通过对T应用约束来暴露任何信息的情况下编译它。具体来说,编译器如何知道为数组分配了多少字节,因为T可能不是引用类型,而是值类型(即struct)?此外,赋值操作result[i] = map(cultures[i])的语义似乎取决于T是引用类型还是值类型。

3 个答案:

答案 0 :(得分:9)

CultureInfoT之间没有任何依赖关系。所有T都同样有效,因为您处理&#34;转换&#34;使用您的自定义Func<CultureInfo, T>

我认为您对通用符在C#中的工作方式感到困惑。它们不是编译时功能(就像在Java中一样),它们一直存活到运行时。只有当你真正需要一个具体的泛型类型时才编译该类型,到那时它已经知道T是什么。

这当然是C#没有Java List<?>的原因之一。没有&#34;普通的通用类型&#34;,你只是&#34;延迟&#34;类型的具体化 - 一旦你有List<string>List<int>,这两个是完全不同的类型,只有反射告诉你它们来自相同的泛型类型。

答案 1 :(得分:4)

  

具体来说,编译器如何知道为数组分配多少字节,

没有。编译器无需知道这一点。

但是假设newarr指令确实需要以字节为单位给出。然后编译器可以简单地在运行时执行元素类型大小和数组长度的乘法。

  

此外,赋值操作result[i] = map(cultures[i])的语义似乎取决于T是引用类型还是值类型。

它不依赖于此。无论类型如何,都会准确复制map返回的任何内容。如果它是值类型,则表示该值被复制。如果它是引用类型,则表示引用被复制。

答案 2 :(得分:3)

我认为理解这一点的关键最好表达如下:

首先,在编译时,会创建一个通用方法,它只是您在源代码中编写的IL版本。它的类型安全(map返回T,数组单元格类型为T,所以那里没有问题)一切都很好。

现在,在运行时,(概念上至少)第一次使用Select<string>时,例如,JIT编译器会在此时创建一​​个新方法。让我们称之为Select__string这个方法(我认为它实际上通常称为Select'string,但我不希望你认为这对于此目的很重要说明)。在这个新方法中,T的所有实例都替换为string,因此当然在编译方法中,一切都很容易解决 - 分配,数组大小分配等。

接下来你在其他地方做Select<int>。 JIT编译器现在创建一个全新的方法,我们称之为Select__int。同样,T的所有实例都替换为int,因此,数组大小和赋值语义也很容易处理。

泛型类型也是如此。实际使用它们时,List<string>List<int>是两种完全不同的类型。这就是.net泛型如此易于编写和使用的原因。

如果这还不清楚,您能举例说明上面的代码中您认为在编译时是否仍需要知道或约束的内容?