12

私は、C# のジェネリックは、C++ テンプレート (これは私が実際に調べたことはなく、間違っている可能性が非常に高いため、喜んで訂正を受け入れます)。

しかし、私のコーディングでは、正確な反例を思いつきました:

static class Program {
    static void Main()
    {
        Test testVar = new Test();

        GenericTest<Test> genericTest = new GenericTest<Test>();
        int gen = genericTest.Get(testVar);

        RegularTest regTest = new RegularTest();
        int reg = regTest.Get(testVar);

        if (gen == ((object)testVar).GetHashCode())
        {
            Console.WriteLine("Got Object's hashcode from GenericTest!");
        }
        if (reg == testVar.GetHashCode())
        {
            Console.WriteLine("Got Test's hashcode from RegularTest!");
        }
    }

    class Test
    {
        public new int GetHashCode()
        {
            return 0;
        }
    }

    class GenericTest<T>
    {
        public int Get(T obj)
        {
            return obj.GetHashCode();
        }
    }

    class RegularTest
    {
        public int Get(Test obj)
        {
            return obj.GetHashCode();
        }
    }
}

これらのコンソール行は両方とも出力されます。

これが発生する実際の理由は、Object.GetHashCode() への仮想呼び出しが Test.GetHashCode() に解決されないことです。これは、Test のメソッドがオーバーライドではなく新規としてマークされているためです。したがって、Test.GetHashCode() で「new」ではなく「override」を使用した場合、0 が返されるとオブジェクト内のメソッド GetHashCode がポリモーフィックにオーバーライドされ、これは正しくないことはわかっていますが、私の (以前の) 理解によるとT のすべてのインスタンスが Test に置き換えられ、メソッド呼び出しが静的に (またはジェネリック解決時に) 「新しい」メソッドに解決されるため、C# ジェネリックの場合は問題になりませんでした。

だから私の質問はこれです:ジェネリックは C# でどのように実装されていますか? CIL バイトコードはわかりませんが、Java バイトコードは知っているので、オブジェクト指向 CLI 言語が低レベルでどのように機能するかは理解しています。そのレベルで気軽に説明してください。

余談ですが、Java の型消去システムと比較して、誰もが C# のジェネリック システムを常に「True Generics」と呼んでいるため、C# のジェネリックはそのように実装されていると思いました。

4

1 に答える 1

9

ではGenericTest<T>.Get(T)、C# コンパイラは(仮想的に) 呼び出す必要があるものを既に選択しています。object.GetHashCodeこれが実行時に「新しい」GetHashCodeメソッドに解決される方法はありません (メソッドテーブルにスロットをオーバーライドするのではなく、独自のスロットがありますobject.GetHashCode)。

Eric Lippert のWhat's the difference, part one: Generics are not templatesから、問題が説明されています (使用されるセットアップはわずかに異なりますが、レッスンはシナリオにうまく変換されます)。

これは、C# のジェネリックが C++ のテンプレートとは異なることを示しています。テンプレートは、凝った検索と置換のメカニズムと考えることができます。[...] ジェネリック型はそうではありません。ジェネリック型は、まあ、ジェネリックです。オーバーロードの解決を1 回行い、結果を焼き込みます。[...] ジェネリック型用に生成した IL には、呼び出すメソッドが既に選択されています。ジッターは、「この追加情報を使用して C# コンパイラーに今すぐ実行するように要求した場合、別のオーバーロードが選択されることをたまたま知っています。生成されたコードを書き直して、C# コンパイラが最初に生成したコードを無視させてください...」 ジッターは C# のルールについて何も知りません。

そして、あなたの望むセマンティクスの回避策:

ここで、引数の実行時の型に基づいて実行時にオーバーロードの解決を再実行したい場合は、それを行うことができます。それが、C# 4.0 の新しい「動的」機能です。「オブジェクト」を「動的」に置き換えるだけで、そのオブジェクトを含む呼び出しを行うと、実行時にオーバーロード解決アルゴリズムが実行され、コンパイラがすべての実行時間を認識していれば、コンパイラが選択したメソッドを呼び出すコードが動的に吐き出されます。コンパイル時の型。

于 2012-07-11T16:24:56.833 に答える