C#とCLR学習ノート(7)——汎用と汎用制約

17553 ワード

文書ディレクトリ
  • 1汎用概要
  • 1.1意味
  • 1.2汎用型の継承
  • 1.2.1汎用型の継承
  • 1.2.2汎用型の型パラメータの継承
  • 2汎用拘束
  • 2.1コンパイラによる汎用パラメータの検証
  • 2.2汎用制約のタイプ
  • 2.3その他の検証問題
  • 参考文献
  • 1汎用性の概要
    1.1意味
    汎用型を使用する主な目的は三つあります.(1)コード多重化を実現すること;(2)Object類を使用することを避けること.汎用型クラスをインスタンス化する時、Tの実際のタイプ(タイプ実参)を指定する必要があり、このようにタイプの安全を保証すること;(3)Objectによる箱詰め解体を減らし、性能を高める(原理は以下参照).コンパイラにとって、汎用Tは本質的にタイプパラメータ(Type parameter)であり、パラメータとは特殊なプレースホルダである.汎用型はプログラムセットに定義されているため、コードがILにコンパイルされてプログラムセットに置かれると、Tは依然として存在する.例えば、以下の例である.
    Console.WriteLine(typeof(List<>));
    Console.WriteLine(typeof(Dictionary<,>));
    //     
    System.Collections.Generic.List`1[T]
    System.Collections.Generic.Dictionary`2[TKey,TValue]
    

    ここで、後一重引用符(`)後の数字はタイプパラメータの個数を表します.コード実行時、すなわちJIT段階でILがネイティブ言語に翻訳されると、汎用タイプパラメータTが具体的なタイプに置き換えられます.たとえば、Listクラスを使用すると、コード中のTはJIT翻訳後にintタイプになります.そのため、タイプ変換、箱詰め解体は存在しません.パフォーマンスが向上した理由です.
    タイプインスタンスが指定されていない汎用タイプは、インタフェースをインスタンス化できないのと同じオープンタイプであるため、インスタンス化できません.1つのタイプの実パラメータを渡すと、閉じたタイプになり、インスタンス化できます.ケース:
    Object o;
    o = Activator.CreateInstance(typeof(List<int>));//OK
    o = Activator.CreateInstance(typeof(List<>));//  ArgumentException,        
    

    汎用型で最もよく使われる場所は集合クラスです.マイクロソフトは汎用集合を使用することを提案し、非汎用集合を使用することを提案しない.上述したタイプの安全で、性能が高いほか、汎用集合クラスの虚の方法が少なく、実行性能をさらに向上させる.また,汎用集合は一般的により多くの拡張方法を有し,より使いやすい.
    1.2汎用性の継承
    1.2.1汎用型の継承
    汎用クラスは、1つの汎用ベースクラスから派生することができますが、汎用サブクラスは、汎用ベースクラスの汎用タイプを繰り返すか、ベースクラスの汎用タイプを指定する必要があります.たとえば、次のコードについて考察します.
    public class ChildGeneric : Generic<T>{} //    
    public class ChildGeneric<T> : Generic<T>{} //    
    public class ChildGeneric : Generic<int>{} //    
    

    1.2.2汎用型のタイプパラメータの継承
    2つの汎用タイプListListを考察し、そのうちMyChildClassMyBaseClassから派生しているが、ListListの間には何の関係があるのだろうか.答えは関係ない.
    タイプパラメータの継承関係は、汎用タイプの継承関係を変更しないため、あるいは、汎用タイプは汎用タイプパラメータの継承関係を破壊する.
    より具体的には、指定したタイプの実パラメータは階層に影響しません.ListObjectから派生し、ListListはいずれもObjectから派生し、両者は「同輩」である.指定タイプ実パラメータは、JIT時に指定したタイプをTに置き換えるだけであり、この2つのリストは2つの異なるクラスである.
    ここからもう一つの話題、すなわちインバータとコヒーレンスを引き出し、詳しくは私のもう一つの文章「C#とCLR学習ノート(6)--コヒーレンスとインバータを簡単に理解する」を参照してください.
    2汎用拘束
    2.1コンパイラによる汎用パラメータの検証
    汎用パラメータTは定義時に指定されていないため、コンパイラはセキュリティのためにコンパイル時に分析し、コードが将来指定可能な任意の汎用タイプの実パラメータに適用されることを保証します.たとえば、次のコードについて考察します.
    private static T Min<T>(T o1, T o2)
    {
        if (o1.CompareTo(o2) < 0)
            return o1;
        return o2;
    }
    

    上記のコードのコンパイルに失敗したのは、すべてのタイプがIComparableインタフェースを実現しているわけではないため、CompareTo()メソッドが実行できないリスクがある.
    だから表面的には、汎用型を使うことはあまりできないようで、Objectで定義された方法しか使えません.明らかに、実際の状況はそうではありません.汎用制約メカニズムがあるため、汎用を有用にします.
    上記のケースを改善すると、スムーズにコンパイルできます.
    private static T Min<T>(T o1, T o2) where T : IComparable<T>
    {
        if (o1.CompareTo(o2) < 0)
            return o1;
        return o2;
    }
    

    コンストレイントは、汎用タイプまたは汎用メソッドに適用できます.ベースクラスまたは書き換え/実装されたメソッドが汎用制約を持っている場合、サブクラスまたはそのメソッドは同じ汎用制約を適用する必要があります.例:
    public class Generic<T> where T : struct
    {}
    public class ChildGeneric<T> : Generic<T> //    
    {}
    public class ChildGeneric<T> : Generic<T> where T : struct //    
    {}
    
    
    public interface IGeneric2
    {
         string GetTInfo<T>() where T : struct;
    }
    class ChildGeneric : IGeneric2
    {
         public string GetTInfo<T>() where T : class //    
         {
              return "";
         }
    }
    class ChildGeneric : IGeneric2
    {
         public string GetTInfo<T>() where T : struct//    
         {
              return "";
         }
    }
    

    2.2汎用制約のタイプ
    ぶんかつ
    形式
    意味
    プライマリコンストレイント(最大1つ指定)where T : struct
    Tは値タイプでなければならない(Nullableを除き、詳細は参考文献参照)where T : class
    Tは参照タイプでなければなりませんwhere T : Foo
    TはFooクラスから派生しなければならない
    マイナーコンストレイント(複数指定可能)where T : IFoo
    TはIooインタフェースを実現しなければならないwhere T1 : T2
    T 1は、汎用型T 2から派生する
    コンストラクタコンストレイントwhere T : new()
    Tは共通の無パラメトリック構造関数を持つ非抽象クラスである.
    2.3その他の検証問題
    (1)タイプ変換の問題
    汎用パラメータは、コンストレイントに合致することを保証するためにタイプ変換されます.できるだけasを使用して変換します.
    public void DoSomething<T>(T obj)
    {
        int x = (int)obj; //    
        int x = (int)(Object)obj; //    ,        InvalidCastException
        stirng x = obj as string; //  
    }
    

    (2)汎用タイプをデフォルトにするdefaultキーワードの使用を推奨します.
    public void DoSomething<T>()
    {
        T temp = null; //    ,     T : class
        T temp = default(T); //  
    }
    

    (3)2つの汎用パラメータの比較
    汎用コンストレイントを指定する必要があります.
    public void DoSomething<T>(T o1, T o2)
    {
        if (o1 == o2){} //    ,    T     ,         ==    。
    }
    

    (4)汎用型を特定の値タイプに拘束することはできない.1つは、値タイプが暗黙的に密封され、継承されないためである.2つは、このような場合に汎用型を使用すると意味がないためである.
    参考文献
    [1]『CLR via C#』第4版