21

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[]

4

3 に答える 3

25

アップデート:

FindInterfaceWithインターフェースであるときに型自体を考慮に入れる限り、基本クラスは必ずしも関与しないため、単純化でき、フラット化された型階層を構築することは冗長になります。そこで、拡張メソッドを追加しましたGetInterfaces(bool)。カバレッジのルールによってインターフェイスを並べ替えることができるため、並べ替えられたインターフェイスの交差が候補になります。それらのすべてが等しく良い場合、私はそれらのどれもが最良のものとは見なされないと言いました。そうでない場合は、最良のものが他の1つをカバーする必要があります。そして、それらはソートされているため、この種の関係は、配列内の右端の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つはFindInterfaceWith、もう1つは重要なメソッドです。これは、使用法が異なるクラスGetTypesArrayという名前のメソッドがすでに存在するためです。GetTypeArrayType

これは、 AkimがGetClassHierarchyを提供したメソッドのように機能します。ただし、このバージョンでは、次のような配列を作成します。

  • 階層の出力

    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>実装などの一部のインターフェイス実装の関係はICollection<int>、この図の線とリンクされていないことに注意してください。

返される配列のインターフェイスは、によってArray.Sort提供される順序付けルールで並べ替えられGetCoverageComparisonます。

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

  • ノート

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

    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は冗長な方法になります。FindInterfaceWithとの唯一の違いFindAssignableWithは次のとおりです。

    FindInterfaceWithnullクラスの最良の選択があった場合に戻ります。whileFindAssignableWithは、正確なクラスを直接返します。

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


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

  • デュアルルール

    1. ソースインターフェイス配列内の2つのインターフェイスを比較します。それぞれが、ソース内の他のインターフェイスの数をカバーしています。CountOverlapped

    2. ルール1がそれらを区別しない場合(returns 0)、2番目の順序は、呼び出しCountOccurrenceて、どれが他のユーザーにさらに何度も継承されているかを判別してから比較することです。

      Linq2つのルールはクエリと同等です。

      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とのような関係の可能性があるため、私の実装ではできない可能性があるのは、上記のように呼び出すIAlphaことIBravoです。

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'2は両方Dictionary<String, String>です。正確な型名を提示する作業をしなかったことをお詫びします。

于 2013-01-23T03:45:11.200 に答える
2

最も単純なケースは、次のように、1つのオブジェクトの基本タイプを反復処理し、それらが他のタイプに割り当て可能かどうかをチェックすることです。

  • コード

    public Type GetClosestType(Type a, Type b) {
        var t=a;
    
        while(a!=null) {
            if(a.IsAssignableFrom(b))
                return a;
    
            a=a.BaseType;
        }
    
        return null;
    }
    

System.Objectこれにより、両方がクラスである場合、無関係な2つのタイプが生成されます。この動作が要件を満たしているかどうかはわかりません。

より高度なケースでは、と呼ばれるカスタム拡張メソッドを使用していIsExtendablyAssignableFromます。

さまざまな数値型、ジェネリック、インターフェイス、ジェネリックパラメーター、暗黙の変換、null許容、ボックス化/アンボックス化、および独自のコンパイラーを実装するときに遭遇したほとんどすべての型を処理できます。

コードを別のgithubリポジトリ[ここ]にアップロードしたので、プロジェクトで使用できます。

于 2013-03-19T12:04:04.373 に答える
1

基本クラスのみを見ると、問題は簡単であり、Impworksの答え(「1つのオブジェクトの親を繰り返し、他のタイプに割り当て可能かどうかを確認する」)によって解決策が得られます。

ただし、インターフェイスも含めたい場合は、自分の例を参考にして、問題に対する独自の解決策はありませIDeltaICharlie。2つ以上のインターフェースは簡単に同じように「優れた」ものになる可能性があるため、単一の最善の解決策はありません。インターフェイスの継承の任意の複雑な図(グラフ)を簡単に作成できます。このような図から、明確に定義された「FindAssignableWith」がないことが簡単にわかります。

また、C#の共変性/反変性は、ジェネリック型の分散の種類に使用されます。例を挙げましょう。私たちが持っていると仮定します

type1: System.Func<string>
type2: System.Func<Tuple<int>>

もちろん、基本クラスでは、「FindAssignableWith」は次のようになります。

solutionA: System.MulticastDelegate

ただし、型Func<out T>はその型パラメーターの共変( )でもあります。したがって、タイプoutT

solutionB: System.Func<System.Object>

IsAssignableFromまた、2つの与えられたタイプtype1とという意味での解決策type2です。しかし、同じことが言えます

solutionC: System.Func<System.IComparable>

とが両方stringであるため、これは機能します。Tuple<>IComparable

したがって、一般的なケースでは、独自の解決策はありません。したがって、必要なものを説明する正確なルールを指定しない限り、ソリューションを見つけるアルゴリズムを思い付くことができません。

于 2013-03-19T21:57:53.780 に答える