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

Lin*_*gxi 7 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;
}
Run Code Online (Sandbox Code Playgroud)

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

Lua*_*aan 9

有之间没有相关性CultureInfoT任何责任.所有T这些都同样有效,因为您使用自定义处理"转换" Func<CultureInfo, T>.

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

这当然是C#没有Java的原因之一List<?>.有没有"共同的通用型",你只有"延迟"类型的具体化-一旦你有List<string>List<int>,两者是完全不同的类型,而只反射告诉你他们来自同一个泛型类型.

  • @Lingxi:是的,*仅当*开始调用T的成员时,这些成员依赖于T的确切含义,或者何时开始分配取决于T是值还是引用类型的值.如果你不在通用方法中做任何这些,那么T是什么并不重要. (2认同)

Ric*_*ons 3

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

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

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

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

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

如果这还不清楚,您能否举例说明在上面的代码中您认为在编译时仍需要了解或限制的具体内容?