什么是具体化?

Mar*_*ijn 157 c# generics reification

我知道Java通过擦除实现了参数多态(泛型).我明白擦除是什么.

我知道C#通过具体化实现参数多态.我知道那可以让你写作

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
Run Code Online (Sandbox Code Playgroud)

或者您可以在运行时知道某些参数化类型的类型参数是什么,但我不明白它什么.

  • 什么是具体类型?
  • 什么是具体价值?
  • 当一个类型/值被确定时会发生什么?

The*_*kis 201

具体化是一个抽象的东西,创造一个具体的东西的过程.

C#泛型中的术语" reification"是指将泛型类型定义和一个或多个泛型类型参数(抽象事物)组合在一起以创建新泛型类型(具体事物)的过程.

到短语它不同,它是采取的定义的处理List<T>int与生产混凝土List<int>的类型.

要进一步了解它,请比较以下方法:

  • 在Java泛型中,泛型类型定义基本上转换为在所有允许类型参数组合中共享的一个具体泛型类型.因此,多个(源代码级别)类型被映射到一个(二进制级别)类型 - 但结果,在该实例中丢弃了关于实例的类型参数的信息(类型擦除).

    1. 作为此实现技术的副作用,本机允许的唯一泛型类型参数是可以共享其具体类型的二进制代码的那些类型; 这意味着那些存储位置具有可互换表示的类型; 这意味着参考类型.使用值类型作为泛型类型参数需要装箱(将它们放在一个简单的引用类型包装器中).
    2. 没有代码重复,以便以这种方式实现泛型.
    3. 在运行时(使用反射)可用的类型信息将丢失.反过来,这意味着泛型类型的特化(对任何特定通用参数组合使用专用源代码的能力)非常有限.
    4. 此机制不需要运行时环境的支持.
    5. 有一些解决方法可以保留 Java程序或基于JVM的语言可以使用的类型信息.
  • 在C#泛型中,泛型类型定义在运行时在内存中维护.每当需要新的具体类型时,运行时环境就会组合泛型类型定义和类型参数,并创建新类型(具体化).因此,我们在运行时为每个类型参数组合获取一个新类型.

    1. 此实现技术允许实例化任何类型的参数组合.使用值类型作为泛型类型参数不会导致装箱,因为这些类型获得自己的实现.(拳击仍然存在于C#中,当然 - 但它发生在其他场景中,而不是这个场景.)
    2. 代码重复可能是一个问题 - 但实际上并非如此,因为足够智能的实现(包括Microsoft .NETMono)可以共享某些实例的代码.
    3. 维护类型信息,通过使用反射检查类型参数,可以在一定程度上进行特化.但是,由于任何具体化发生之前编译泛型类型定义(这是通过根据类型参数的约束编译定义来完成的- 因此,编译器必须能够,因此专业化程度是有限的.即使在没有特定类型参数的情况下,"理解"该定义).
    4. 这种实现技术在很大程度上依赖于运行时支持和JIT编译(这就是为什么你经常听到C#泛型在iOS等平台上有一些限制,因为动态代码生成受到限制).
    5. 在C#泛型的上下文中,运行时环境为您完成了具体化.但是,如果您想更直观地理解泛型类型定义和具体泛型类型之间的区别,您始终可以使用System.Type该类自行执行具体化(即使您实例化的特定泛型类型参数组合也没有' t直接出现在您的源代码中).
  • 在C++模板中,模板定义在编译时保存在内存中.每当源代码中需要新的模板类型实例化时,编译器就会组合模板定义和模板参数,并创建新类型.因此,我们在编译时为模板参数的每个组合获取一个唯一类型.

    1. 此实现技术允许实例化任何类型的参数组合.
    2. 众所周知,这会复制二进制代码,但是足够智能的工具链仍然可以检测到这一点,并为某些实例共享代码.
    3. 模板定义本身不是"编译"的 - 只是实际编译了它的具体实例.这减少了对编译器的约束,并允许更大程度的模板特化.
    4. 由于模板实例化是在编译时执行的,因此这里也不需要运行时支持.
    5. 这个过程最近被称为单态化,特别是在Rust社区.这个词与参数多态性形成对比,参数多态性是泛型来源的概念的名称.

  • 与C++模板进行了很好的比较......它们似乎介于C#和Java的泛型之间.你有不同的代码和结构来处理不同的特定泛型类型,比如在C#中,但它们都是在编译时完成的,就像在Java中一样. (7认同)
  • 此外,在C++中,这使得能够引入模板特化,其中每个(或仅一些)具体类型可以具有不同的实现.显然在Java中是不可能的,但在C#中也是如此. (3认同)

Jon*_*nna 27

物化一般是指(外计算机科学)"做出实实在在的事."

在编程中,东西具体化,如果我们能够获得关于它的语言本身的信息.

对于两个完全非泛型相关的C#所做的事情并没有具体化的例子,让我们采取方法和内存访问.

OO语言通常有方法,(并且许多方法没有类似但不绑定类的函数).因此,您可以使用这种语言定义方法,调用它,或者覆盖它,等等.并非所有这些语言都允许您将方法本身作为数据处理程序.C#(实际上,.NET而不是C#)确实允许您使用MethodInfo表示方法的对象,因此在C#方法中进行了实现.C#中的方法是"第一类对象".

所有实用语言都有一些方法来访问计算机的内存.在像C这样的低级语言中,我们可以直接处理计算机使用的数字地址之间的映射,所以喜欢的int* ptr = (int*) 0xA000000; *ptr = 42;是合理的(只要我们有充分的理由怀疑0xA000000以这种方式访问内存地址赢了'吹东西).在C#中这是不合理的(我们可以在.NET中强制它,但是随着.NET内存管理的推移,它不太可能有用).C#没有具体的内存地址.

因此,由于refied意味着"变成现实",所以"具体类型"是我们可以用所讨论的语言"谈论"的类型.

在泛型中,这意味着两件事.

一个是List<string>类型string或类似的类型int.我们可以比较那种类型,得到它的名字,并查询它:

Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True
Run Code Online (Sandbox Code Playgroud)

这样做的结果是我们可以在方法本身中"讨论"泛型方法(或泛型类的方法)参数的类型:

public static void DescribeType<T>(T element)
{
  Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime
}
Run Code Online (Sandbox Code Playgroud)

作为一项规则,这样做太多是"臭",但它有许多有用的案例.例如,看看:

public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) throw Error.ArgumentNull("source");
  Comparer<TSource> comparer = Comparer<TSource>.Default;
  TSource value = default(TSource);
  if (value == null)
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      do
      {
        if (!e.MoveNext()) return value;
        value = e.Current;
      } while (value == null);
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  else
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  return value;
}
Run Code Online (Sandbox Code Playgroud)

这并没有TSource对不同行为的类型和各种类型之间进行大量比较(通常表示您根本不应该使用泛型),但它确实在可以存在的类型的代码路径之间进行了分割null(null如果没有找到任何元素,并且如果其中一个元素被比较,则不得进行比较以找到最小值; null以及不能存在的类型的代码路径null(如果没有找到元素则应该抛出,并且不必担心null元素的可能性).

因为TSource在方法中是"真实的",所以这种比较可以在运行时或jitting时间进行(通常是jitting时间,当然上面的情况会在jitting时间这样做而不会产生未采用的路径的机器代码)并且我们有一个为每个案例分离方法的"真实"版本.(虽然作为优化,机器代码针对不同的参考类型类型参数的不同方法共享,因为它可以不影响这一点,因此我们可以减少机器代码的数量).

(除非你也处理Java,否则谈论C#中泛型类型的具体化是不常见的,因为在C#中我们只是认为这种形式是理所当然的;所有类型都是有效的.在Java中,非泛型类型被称为具体化,因为它是它们和泛型类型之间的区别.


Lua*_*aan 15

正如duffymo已经指出的那样,"具体化"并不是关键区别.

在Java中,泛型基本上可用于改进编译时支持 - 它允许您在代码中使用强类型的集合,并为您处理类型安全.但是,这仅存在于编译时 - 编译后的字节码不再具有任何泛型概念; 所有泛型类型都转换为"具体"类型(object如果泛型类型是无界的,则使用),根据需要添加类型转换和类型检查.

在.NET中,泛型是CLR的一个不可或缺的特性.编译泛型类型时,它在生成的IL中保持通用.它不仅仅像Java一样转换为非泛型代码.

这对仿制药在实践中的运作方式有几个影响.例如:

  • Java必须SomeType<?>允许您传递给定泛型类型的任何具体实现.C#不能这样做 - 每个特定的(具体化的)泛型类型都是它自己的类型.
  • Java中的无界泛型类型意味着它们的值存储为object.在此类泛型中使用值类型时,这会对性能产生影响.在C#中,当您在泛型类型中使用值类型时,它将保持值类型.

为了给出一个示例,假设您有一个List带有一个泛型参数的泛型类型.在Java中,List<String>并且List<Int>最终将在运行时成为完全相同的类型 - 泛型类型仅存在于编译时代码中.以如所有通话GetValue将被转化为(String)GetValue(Int)GetValue分别.

在C#中,List<string>List<int>有两种不同的类型.它们不可互换,并且它们的类型安全性也在运行时强制执行.无论你做什么,new List<int>().Add("SomeString")都将无法工作 - 底层存储实际上List<int>是一些整数数组,而在Java中,它必然是一个数组.在C#中,没有涉及演员表,没有拳击等.object

这也应该说明为什么C#不能用Java做同样的事情SomeType<?>.在Java中,"派生自"的所有泛型类型SomeType<?>最终都是完全相同的类型.在C#中,所有各种特定的SomeType<T>s都是它们自己独立的类型.删除编译时检查,可以传递SomeType<Int>而不是SomeType<String>(实际上,所有这些SomeType<?>意味着"忽略给定泛型类型的编译时检查").在C#中,这是不可能的,即使是派生类型不(即,你不能做List<object> list = (List<object>)new List<string>();但即使string是源自object).

两种实现都有其优点和缺点.曾经有好几次我曾经喜欢能够SomeType<?>在C#中作为一个参数 - 但它对C#泛型的工作方式没有意义.

  • 好吧,您可以在C#中使用类型`List <>`,`Dictionary <,>`等等,但是它与给定的具体列表或字典之间的差距需要相当多的反思来桥接.接口上的差异确实有助于我们曾经想要轻松弥合这一差距的一些情况,但不是全部. (2认同)
  • @JonHanna您可以使用`List <>`来实例化一个新的特定泛型类型 - 但它仍然意味着创建您想要的特定类型.但是你不能使用`List <>`作为参数.但是,是的,至少这可以让你用反射弥合差距. (2认同)