72

この質問により、ジェネリック メソッドの具体的な実装が実際に存在する場所について疑問に思いました。私はグーグルを試しましたが、正しい検索が思いつきません。

この簡単な例を挙げると:

class Program
{
    public static T GetDefault<T>()
    {
        return default(T);
    }

    static void Main(string[] args)
    {
        int i = GetDefault<int>();
        double d = GetDefault<double>();
        string s = GetDefault<string>();
    }
}

私の頭の中では、ある時点で必要な3つの具象実装を備えた実装が得られると常に想定しており、単純な疑似マングリングでは、使用される特定の型が正しいスタック割り当てなどをもたらすこの論理的具象実装が得られます。 .

class Program
{
    static void Main(string[] args)
    {
        int i = GetDefaultSystemInt32();
        double d = GetDefaultSystemFloat64();
        string s = GetDefaultSystemString();
    }

    static int GetDefaultSystemInt32()
    {
        int i = 0;
        return i;
    }
    static double GetDefaultSystemFloat64()
    {
        double d = 0.0;
        return d;
    }
    static string GetDefaultSystemString()
    {
        string s = null;
        return s;
    }
}

ジェネリック プログラムの IL を見ると、まだジェネリック型で表現されています。

.method public hidebysig static !!T  GetDefault<T>() cil managed
{
  // Code size       15 (0xf)
  .maxstack  1
  .locals init ([0] !!T CS$1$0000,
           [1] !!T CS$0$0001)
  IL_0000:  nop
  IL_0001:  ldloca.s   CS$0$0001
  IL_0003:  initobj    !!T
  IL_0009:  ldloc.1
  IL_000a:  stloc.0
  IL_000b:  br.s       IL_000d
  IL_000d:  ldloc.0
  IL_000e:  ret
} // end of method Program::GetDefault

では、どのように、どの時点で、int、次に double、次に string をスタックに割り当て、呼び出し元に返す必要があると判断されるのでしょうか? これは JIT プロセスの操作ですか? 私はこれを完全に間違った見方で見ていますか?

4

2 に答える 2

78

C# では、ジェネリック型とメソッドの概念はランタイム自体でサポートされています。C# コンパイラは、ジェネリック メソッドの具体的なバージョンを実際に作成する必要はありません。

実際の「具体的な」ジェネリック メソッドは、JIT によって実行時に作成され、IL には存在しません。型でジェネリック メソッドが初めて使用されると、JIT はそれが作成されているかどうかを確認し、作成されていない場合は、そのジェネリック型に適切なメソッドを構築します。

これは、ジェネリックと C++ のテンプレートなどとの根本的な違いの 1 つです。これは、ジェネリックに関する多くの制限の主な理由でもあります。コンパイラは実際には型のランタイム実装を作成していないため、インターフェイスの制限はコンパイル時間の制約によって処理されます。これにより、ジェネリックは C++ のテンプレートよりも制限が厳しくなります。潜在的なユースケースの。ただし、それらがランタイム自体でサポートされているという事実により、C++ やその他のコンパイル時に作成されたテンプレート実装ではサポートされていない方法で、ジェネリック型の作成とライブラリからの使用が可能になります。

于 2013-10-16T18:14:42.223 に答える
45

ジェネリック メソッドの実際のマシン コードは、通常どおり、メソッドが jit されるときに作成されます。その時点で、ジッターはまず、適切な候補が以前にジッターされたかどうかをチェックします。これは非常に一般的なケースで、具体的な実行時型 T が参照型であるメソッドのコードは、一度だけ生成する必要があり、可能なすべての参照型 T に適しています。 T の制約により、このマシン コードが常に有効であることが保証されます。以前に C# コンパイラによってチェックされました。

値の型である T に対して追加のコピーが生成される場合があります。T の値はもはや単純なポインターではないため、それらのマシン コードは異なります。

そうです、あなたの場合、3つの異なる方法になります。<string>バージョンはどの参照タイプにも使用できますが、他のタイプはありません。およびバージョンは<int><double>「値型である T」のカテゴリに適合します。

それ以外の場合は、これらのメソッドの戻り値が異なる方法で呼び出し元に返されるという優れた例です。x64 ジッターでは、string バージョンは、返されるポインター値と同様に、RAX レジスターで値を返し、int バージョンは EAX レジスターで戻り、double バージョンは XMM0 レジスターで戻ります。

于 2013-10-16T18:16:07.293 に答える