C#汎用型と依頼について話し始めます(一)

6919 ワード

前回はC#コンパイラとJITコンパイラについて簡単にお話ししましたが、もともとタイプ、オブジェクト、スレッドスタックとホスティングスタックの実行時の相互関係を書き続けたいと思っていましたが、残念ながらこの部分は図面で説明したほうがいいので、先にスキップしました.
JAVAでは、汎用型はJAVAコンパイラのみでサポートされており、JVMにはサポートされていない.つまり、汎用型を表す新しいバイトコードが定義されていないため、JVMにも新しいバイトコードをサポートする新しい命令は自然に存在しない.類比到NETにとって、つまりC#コンパイラによってサポートされ、CLRによってサポートされない.これで多くの面白い問題が発生しました.私达はすべて私达のコードがすべてコンパイラの翻訳を通じて(通って)変えなければならないことを知っていて、JAVAの中の泛型はJAVAのコンパイラがタイプの消去の方式を采用して泛型を実现します.定義された汎用タイプは、対応する元のタイプ(raw type)が自動的に提供されます.元のタイプの名前は、タイプパラメータを削除した汎用名で、タイプ変数を消去し、限定タイプ(無限定の変数はObject)に置き換え、文法糖と見なすことができます.例:
public class MyHashMap<TKey, TValue> {

    private HashMap<TKey, TValue> m_map = new HashMap<TKey, TValue>();

    

    public TValue get(TKey key) {

        return this.m_map.get(key);

    }

    

    public void put(TKey key, TValue value) {

        this.m_map.put(key, value);

    }



    public static void main(String[] args) {

        MyHashMap<String, Integer> map = new MyHashMap<String, Integer>();

        map.put("Hello", 5);

        int i = map.get("Hello");

    }    

}


バイトコードにコンパイルすると、次のようになります(ここではJAVAコードで表します)

  
    
public class MyHashMap {
private HashMap m_map = new HashMap();

public Object get(Object key) {
return this .m_map.get(key);
}

public void put(Object key, Object value) {
this .m_map.put(key, value);
}

public static void main(String[] args) {
MyHashMap map
= new MyHashMap();
map.put(
" Hello " , 5 );
int i = (Integer)map.get( " Hello " );
}
}

はい、Objectを見て、私はまた箱詰めを思い出したことを認めて、Javaの中の汎用型が箱詰めの問題を解決していないことを見ることができます.JVMは汎用タイプを知らないため、JAVAではJAVAコンパイラの文法糖として表現されている.私がJAVAに触れたばかりの頃は、確かに次のような間違いに戸惑っていました.
public class MyClass<SomeType> {

    public static void myMethod(Object item) {

        if (item instanceof SomeType) { //   

            ...

        }

        SomeType st = new SomeType(); //   

        SomeType[] myArray = new SomeType[10]; //   

    }

}


ここで考えてみましょう.いったいどうすれば本格的なサポート汎用型になるのでしょうか.はい.NETでは最終的にCLRによってメタデータに基づいてILコードが実行されるので、容易に理解できる.
1.ILには必ず「タイプパラメータ」を識別する新しい命令があります.
2.タイプとメソッドの定義はメタデータテーブルに対応する表示があることを知っています.そのため、汎用型をサポートするために、メタデータのフォーマットも変更されます.
3.JITコンパイラを修正して新しいIL命令を実行する.
すなわち,汎用型定義はMSIL型に完全にコンパイルできる.
汎用型の実行の大まかな流れは次のとおりです.
C#コンパイラはILとメタデータを生成し、汎用クラス定義を表し、JITコンパイラは汎用タイプ定義と一連のタイプパラメータを組み合わせます.
具体的には,ILはある汎用型のインスタンスを初期化するためにプレースホルダを予約し,JITコンパイラは実行時にマシンコードを生成する際に「定義を補完する」.JITは、対応するILコードをX 86命令にコンパイルしながら最適化する.どんな内容を最適化しましたか?たとえば,タイプパラメータが参照タイプの場合,同じマシンコードで表すことができる.値タイプではなく参照タイプなのはなぜですか?引用タイプは基本的にポインタなので、本質的に構造は同じです.
ここではまたクラスロードについてお話しします.JITは、あるクラスがロードされたときに完全なX 86命令を生成するのではなく、クラス内の各メソッドが最初に呼び出されたときにのみコンパイルを開始する.(私は今、タイプ、オブジェクト、スレッドスタック、管理スタックの実行時の相互関係について話すべきだと思います).これにより、まずILコード上でプレースホルダ置換ステップを実行し、特定のタイプに置き換え、その後、通常のクラスのようにオンデマンドでコンパイルします.
実行する前にプレースホルダが特定のタイプに置き換えられているため、汎用的なマッチング度はかなり高いことがわかります.正確なマッチングと言うべきだ.これはどこに影響しますか?方法が重荷されると体現されます.MyBaseに割り当てられたオブジェクトの場合、WriteMEsaage(T obj)は、WriteMEsaage(MyBaseobj)よりもリロードマッチングが優先されます.TをMyDerivedコンパイラに置き換えることで「正確なマッチング」が完了し、WriteMEsaage(MyBase obj)は暗黙的な変換が必要になるからです.したがって、汎用メソッドは、呼び出し時に明示的なタイプ変換を行わない限り、より有利である.コードで説明します.
public class MyBase



{



}



 



public class MyDerived : MyBase



{



    #region IMessageWriter Members



    void IMessageWriter.WriteMessage()



    {



        Console.WriteLine("Inside MyDerived.WriteMessage");



    }



    #endregion



}



 



class Program



{



    static void WriteMessage(MyBase b)    {



        Console.WriteLine("Inside WriteMessage(MyBase)");



    }



    static void WriteMessage<T>(T obj)



    {



        Console.Write("Inside WriteMessage<T>(T):  ");



        Console.WriteLine(obj.ToString());



    }



    static void Main(string[] args)



    {



        MyDerived d = new MyDerived();



        Console.WriteLine("Calling Program.WriteMessage");



        WriteMessage(d); //              



        Console.WriteLine();



        Console.WriteLine("Cast to base object");



        WriteMessage((MyBase)d);



        Console.WriteLine();



    }



}


したがって、クラスとそのすべての派生クラスをサポートしたい場合は、ベースクラスに基づいて汎用を作成することは最善の選択ではありません.同様に、インタフェースベースも同様です.
では、この場合、実行時に判断する必要があると思います.もちろん、これは最善の解決策ではありません.呼び出し者に具体的な実装を遮断していますが、実行時検査のオーバーヘッドもあります.
Static void WriteMessage<T>(T obj){



         If(obje is MyBase){



                  WriteMessage(obj as MyBase);  //      



         }else {



                  Conslole.Write(“Invoke WriteMessage<T>”)



         }



}