2 つのタイプ間の最適な適合のための最小共変タイプを見つけるにはどうすればよいでしょうか? 質問する

2 つのタイプ間の最適な適合のための最小共変タイプを見つけるにはどうすればよいでしょうか? 質問する

IsAssignableFromある型が別の型から割り当て可能かどうかを示すブール値を返すメソッドがあります。

割り当て可能かどうかをテストするだけでなく、からまたはお互いを知るだけでなく、最小共変量最適なタイプを選択してください。

次の例を考えてみましょう(C# 4.0)

  • コード

    // method body of Func is irrelevant, use default() instead
    Func<char[]> x = default(Func<char[]>);
    Func<int[]> y = default(Func<int[]>);
    
    Func<Array> f = default(Func<Array>);
    Func<IList> g = default(Func<IList>);
    
    g=x;
    g=y;
    
    y=x; // won't compile
    x=y; // won't compile
    
    // following two are okay; Array is the type for the covariance
    f=x; // Array > char[] -> Func<Array> > Func<char[]> 
    f=y; // Array > int[] -> Func<Array> > Func<int[]> 
    
    // following two are okay; IList is the interface for the covariance
    g=x;
    g=y;
    

上記の例では、char[]との間の型を検索しますint[]

ベストアンサー1

アップデート:

結局FindInterfaceWith、単純化でき、フラットな型階層を構築することは冗長になります。インターフェースの場合は型自体を考慮に入れる限り、基本クラスが必ずしも関係するわけではないからです。そこで、拡張メソッドを追加しましたGetInterfaces(bool)。インターフェースはカバレッジのルールでソートできるので、ソートされたインターフェースの共通部分が候補になります。すべてが同等に優れている場合、どれも最適とは見なされないと言いました。そうでない場合、最適なものは他のいずれかをカバーしている必要があります。また、インターフェースはソートされているため、配列の右端の 2 つのインターフェースにこの種の関係が存在し、最も具体的な共通の最適インターフェースがあることを示します。


;を使用するとコードを簡素化できますLinqが、私のシナリオでは、参照と名前空間の要件を可能な限り減らす必要があります。

  • コード

    using System;
    
    public static class TypeExtensions {
        static int CountOverlapped<T>(T[] ax, T[] ay) {
            return IntersectPreserveOrder(ay, ax).Length;
        }
    
        static int CountOccurrence(Type[] ax, Type ty) {
            var a = Array.FindAll(ax, x => Array.Exists(x.GetInterfaces(), tx => tx.Equals(ty)));
            return a.Length;
        }
    
        static Comparison<Type> GetCoverageComparison(Type[] az) {
            return (tx, ty) => {
                int overlapped, occurrence;
                var ay = ty.GetInterfaces();
                var ax = tx.GetInterfaces();
    
                if(0!=(overlapped=CountOverlapped(az, ax).CompareTo(CountOverlapped(az, ay)))) {
                    return overlapped;
                }
    
                if(0!=(occurrence=CountOccurrence(az, tx).CompareTo(CountOccurrence(az, ty)))) {
                    return occurrence;
                }
    
                return 0;
            };
        }
    
        static T[] IntersectPreserveOrder<T>(T[] ax, T[] ay) {
            return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))>=0);
        }
    
        /*
        static T[] SubtractPreserveOrder<T>(T[] ax, T[] ay) {
            return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))<0);
        }
    
        static Type[] GetTypesArray(Type typeNode) {
            if(null==typeNode) {
                return Type.EmptyTypes;
            }
    
            var baseArray = GetTypesArray(typeNode.BaseType);
            var interfaces = SubtractPreserveOrder(typeNode.GetInterfaces(), baseArray);
            var index = interfaces.Length+baseArray.Length;
            var typeArray = new Type[1+index];
            typeArray[index]=typeNode;
            Array.Sort(interfaces, GetCoverageComparison(interfaces));
            Array.Copy(interfaces, 0, typeArray, index-interfaces.Length, interfaces.Length);
            Array.Copy(baseArray, typeArray, baseArray.Length);
            return typeArray;
        }
        */
    
        public static Type[] GetInterfaces(this Type x, bool includeThis) {
            var a = x.GetInterfaces();
    
            if(includeThis&&x.IsInterface) {
                Array.Resize(ref a, 1+a.Length);
                a[a.Length-1]=x;
            }
    
            return a;
        }
    
        public static Type FindInterfaceWith(this Type type1, Type type2) {
            var ay = type2.GetInterfaces(true);
            var ax = type1.GetInterfaces(true);
            var types = IntersectPreserveOrder(ax, ay);
    
            if(types.Length<1) {
                return null;
            }
    
            Array.Sort(types, GetCoverageComparison(types));
            var type3 = types[types.Length-1];
    
            if(types.Length<2) {
                return type3;
            }
    
            var type4 = types[types.Length-2];
            return Array.Exists(type3.GetInterfaces(), x => x.Equals(type4)) ? type3 : null;
        }
    
        public static Type FindBaseClassWith(this Type type1, Type type2) {
            if(null==type1) {
                return type2;
            }
    
            if(null==type2) {
                return type1;
            }
    
            for(var type4 = type2; null!=type4; type4=type4.BaseType) {
                for(var type3 = type1; null!=type3; type3=type3.BaseType) {
                    if(type4==type3) {
                        return type4;
                    }
                }
            }
    
            return null;
        }
    
        public static Type FindAssignableWith(this Type type1, Type type2) {
            var baseClass = type2.FindBaseClassWith(type1);
    
            if(null==baseClass||typeof(object)==baseClass) {
                var @interface = type2.FindInterfaceWith(type1);
    
                if(null!=@interface) {
                    return @interface;
                }
            }
    
            return baseClass;
        }
    }
    

再帰メソッドは 2 つあります。1 つは です。もう 1 つは、の使用法が異なるクラスのという名前のメソッドがすでに存在するため、FindInterfaceWith重要なメソッドです。GetTypesArrayGetTypeArrayType

それは方法のように機能しますアキム提供されたクラス階層を取得する; ただし、このバージョンでは次のような配列が構築されます。

  • 階層の出力

    a[8]=System.String
    a[7]=System.Collections.Generic.IEnumerable`1[System.Char]
    a[6]=System.Collections.IEnumerable
    a[5]=System.ICloneable
    a[4]=System.IComparable
    a[3]=System.IConvertible
    a[2]=System.IEquatable`1[System.String]
    a[1]=System.IComparable`1[System.String]
    a[0]=System.Object
    

ご存知のとおり、それらは特定の順序で並んでおり、それが物事を機能させる仕組みです。GetTypesArray構築された配列は実際にはフラット化されたツリーです。配列は実際には次のようにモデル内にあります。

  • rFbtV.png

    IList<int>この図では、 implementsなどの一部のインターフェース実装の関係がICollection<int>線でリンクされていないことに注意してください。

返される配列内のインターフェースは、Array.Sortによって提供される順序付けルールに従ってソートされますGetCoverageComparison

いくつか言及すべき点がある。例えば、複数のインターフェースの実装いくつかの回答では一度だけでなく、複数の回答で言及されています([これ]); そして、私はそれらを解決するための方法を定義しました。それは次のとおりです。

  • 注記

    1. インターフェースを取得するメソッドは、アルファベット順や宣言順などの特定の順序でインターフェイスを返しません。インターフェイスが返される順序は変化するため、コードはインターフェイスが返される順序に依存してはなりません。

    2. 再帰のため、基本クラスは常に順序付けられます。

    3. 2 つのインターフェースが同じカバレッジを持つ場合、どちらも適格とは見なされません。

      次のようなインターフェースが定義されているとします (またはクラスでも問題ありません)。

      public interface IDelta {
      }
      
      public interface ICharlie {
      }
      
      public interface IBravo: IDelta, ICharlie {
      }
      
      public interface IAlpha: IDelta, ICharlie {
      }
      

      IAlphaでは、との割り当てにはどちらが適しているでしょうかIBravo? この場合、FindInterfaceWithは を返すだけですnull

質問の中で[2 つの型 (重複) の中で割り当て可能な最小の型を見つけるにはどうすればよいでしょうか?]、私は次のように述べました。

  • 間違った推論

    この仮定が正しければ、 は冗長な方法になります。とFindInterfaceWithの唯一の違いは次のようになります。FindInterfaceWithFindAssignableWith

    FindInterfaceWithnullクラスの最適な選択があった場合は を返します。一方、FindAssignableWith正確なクラスを直接返します。

しかし、メソッドを見るとFindAssignableWith、他の 2 つのメソッドを呼び出す必要があるのは元の仮定に基づいており、逆説的なバグは魔法のように消えました。


順序付けインターフェースのカバレッジ比較ルールについては、デリゲートでは以下GetCoverageComparisonを使用します。

  • 二重のルール

    1. ソースインターフェース配列内の2つのインターフェースを比較し、それぞれがソース内の他のインターフェースをいくつカバーしているかを調べるには、CountOverlapped

    2. ルール1がそれらを区別しない場合( を返す0)、二次順序付けは、 を呼び出してCountOccurrence、どれが他のものによってより多く継承されているかを判断し、比較することです。

      2 つのルールは次のLinqクエリと同等です。

      interfaces=(
          from it in interfaces
          let order1=it.GetInterfaces().Intersect(interfaces).Count()
          let order2=(
              from x in interfaces
              where x.GetInterfaces().Contains(it)
              select x
              ).Count()
          orderby order1, order2
          select it
          ).ToArray();
      

      FindInterfaceWith次に、おそらく再帰呼び出しを実行して、このインターフェースが最も一般的なインターフェースとして認識されるのに十分であるか、または や のような単なる別の関係であるかを判断しIAlphaますIBravo

また、 メソッドについてはFindBaseClassWith、返される内容は、いずれかのパラメータが null の場合は null を返すという当初の想定とは異なります。実際には、渡された別の引数を返します。

これは次の質問に関連しています[メソッド `FindBaseClassWith` は何を返すべきでしょうか?] のメソッドチェーンについてFindBaseClassWith。現在の実装では、次のように呼び出すことができます。

  • メソッド連鎖

    var type=
        typeof(int[])
            .FindBaseClassWith(null)
            .FindBaseClassWith(null)
            .FindBaseClassWith(typeof(char[]));
    

    返されますtypeof(Array)。この機能のおかげで、

    var type=
        typeof(String)
            .FindAssignableWith(null)
            .FindAssignableWith(null)
            .FindAssignableWith(typeof(String));
    

    私の実装では、やFindInterfaceWithのような関係の可能性があるため、上記のように を呼び出すことはできない可能性があります。IAlphaIBravo

FindAssignableWith次のような例を呼び出して、いくつかの状況でコードをテストしました。

  • 割り当て可能な型の出力

    (Dictionary`2, Dictionary`2) = Dictionary`2
    (List`1, List`1) = IList
    (Dictionary`2, KeyValuePair`2) = Object
    (IAlpha, IBravo) = <null>
    (IBravo, IAlpha) = <null>
    (ICollection, IList) = ICollection
    (IList, ICollection) = ICollection
    (Char[], Int32[]) = IList
    (Int32[], Char[]) = IList
    (IEnumerable`1, IEnumerable`1) = IEnumerable
    (String, Array) = Object
    (Array, String) = Object
    (Char[], Int32[]) = IList
    (Form, SplitContainer) = ContainerControl
    (SplitContainer, Form) = ContainerControl
    

    テストが とList'1表示されるのは、 ;を使用IListしてテストし、 が両方とも だからです。正確な型名を提示する作業を行っていなかったことをお詫びします。typeof(List<int>)typeof(List<String>)Dictionary'2Dictionary<String, String>

おすすめ記事