8

2 つの非ジェネリックTypeオブジェクトabがある場合、関数を使用して a が b から代入可能かどうかを簡単に確認できa.IsAssignableFrom(b)ます。

class ClassBase { }
class ClassDerived : ClassBase { }
...
typeof(ClassBase).IsAssignableFrom(typeof(ClassDerived)) //returns true

ここで、2 つの汎用インターフェイスがあるとします。

interface IBase<T> { }
interface IDerived<T> : IBase<T> { }

それらを閉じると、以前と同じことをまったく同じ動作で行うことができます。

typeof(IBase<object>).IsAssignableFrom(typeof(IDerived<object>)) //returns true

実際、T閉じるために使用できるものはすべて閉じるためにもIDerived使用でき、 (その特定の T に対して)から割り当てることができます。IBaseIBase<T>IDerived<T>

でも、

typeof(IBase<>).IsAssignableFrom(typeof(IDerived<>)) //returns false

それがなぜそうなのか、私にはある程度の考えがあります(それらはさまざまなタイプで閉じられ、変換できなくなる可能性がありますか?)。したがって、この場合に true を返す関数は多少異なることを理解しています。問題は、「すべての有効IBase<T>な から割り当て可能ですか?」です。( hvdのおかげで)IDerived<T>T

私が考えたのは、ジェネリックを閉じてから、それらが割り当て可能かどうかを尋ねることです。しかし、一般的に言うと、最もジェネリックな型で閉じる必要がありbます。

もう 1 つのアプローチは、実装/継承ツリーを上ってbと比較することaです。

私の質問は、一般的な場合でもこれを行う簡単な方法があるかどうかです。

動機:最後にこれを実際に必要としないので、一般的な関心。ただし、これが最初に必要になったのは、オープン ジェネリックとクローズ ジェネリックで Ninject を使用していたときで、オープン ジェネリック クラスをオープン ジェネリック インターフェイス (クラス) にキャストできるかどうかを解決する必要がありました。

4

3 に答える 3

1

すでにわかっているようにtypeof(IBase<>).IsAssignableFrom(typeof(IDerived<>))、2 つのオープン ジェネリック型は相互の継承階層にないため、決して true を返しません。

私の質問は、一般的な場合でもこれを行う簡単な方法があるかどうかです。

いいえ、簡単ではありませんが...

T代入可能性をチェックしている 2 つのジェネリック型のいずれにも制約 ( ) がない場合は、型パラメーターとして使用してから、構築された型where T: ...を使用して、閉じたジェネリック型を構築できると思います。objectIsAssignableFrom

がいずれかのジェネリック型に制約されている場合Tは、リフレクションを使用してそれらの制約 ( Type.GetGenericArgumentsType.GetGenericParameterConstraints ) を見つけ、その情報を使用してジェネリック型を構築する必要があります。このシナリオでは、2 つのジェネリック型の間で代入可能性を持たせるために、A<T> : B<T>継承 (同じ) のため、制約型は依然として同じでなければなりません。T一方の制約型が他方を継承している場合、2 つの制約型の最も派生した型で両方を構築すると、ジェネリック型の代入可能性が見つかることに注意してください。

ここではいくつかの例を示します。

    public class A<T> {}
    public class B<T> : A<T> {}

    public class C<T> where T: E {}
    public class D<T> : C<T> where T: F {}

    public class E {}
    public class F : E {}
    public class G : F {}

    typeof(A<>).IsAssignableFrom(typeof(B<>))              // false
    typeof(A<object>).IsAssignableFrom(typeof(B<object>))  // true
    typeof(A<string>).IsAssignableFrom(typeof(B<string>))  // true
    typeof(C<E>).IsAssignableFrom(typeof(D<F>))            // false
    typeof(C<F>).IsAssignableFrom(typeof(D<F>))            // true
    typeof(C<G>).IsAssignableFrom(typeof(D<G>))            // true
于 2012-08-06T14:42:21.840 に答える
1

別のジェネリックから直接継承するジェネリック インターフェイスの例は、オープン ジェネリック間の互換性を解決する際の複雑さを覆い隠しています。詳細については、Eric Lippert の最近のブログ エントリを参照してください.

リンクされた記事の Eric のメモに従って、ジェネリック インターフェイスがすべての場合に相互に割り当て可能かどうかを判断する一般的なケースを解決しようとはしません。その解決策のかなりの部分では、2 つの型に対する制約 (存在する場合) が交差するかどうかを判断する必要があります。また、1 つの開いているジェネリック インターフェイスが別のケースに割り当て可能である場合とそうでない場合がある場合に、仮想メソッドが何を返すかを決定する必要あります。これは、重複しているが一致しない制約がある場合に発生します。

更新しました

直接継承を比較する場合、「ツリーを上る」ことは、拡張メソッドにパッケージ化すると非常に簡単です。GetInterfacesただし、2 つのオープン ジェネリック型が等しいかどうかを実際に判断するには、独自の比較を定義する必要があります。これは、ジェネリック型によって取得またはBaseType呼び出された型定義では、組み込みの等価比較が機能しないためです。

typeof(Base<>) == typeof(Derived<>).BaseType; // Returns false
typeof(IBase<>) == typeof(Base<>).GetInterfaces()[0]; // Returns false

BaseTypeこれはおそらく、オープン ジェネリック型が null プロパティから取得されるか、GetInterfaces()nullFullNameプロパティを持つという事実に由来します。したがって、完全なアセンブリ名を含めるかどうかを決定するオプションのパラメーターを使用して、独自の拡張メソッドによっても定義します。NamespaceNameGetFullName()strongName

したがって、オープンジェネリック型間の直接継承または実装を比較するためのかなりコンパクトな実装を次に示します。

public static class TypeExtensions {
    public static bool OpenIsAssignableFrom(this Type baseType, Type c, bool strongName = true) {
        if (!baseType.IsGenericTypeDefinition || !c.IsGenericTypeDefinition) return false;
        if (baseType.IsInterface)
            return c.ImplementsOpenInterface(baseType);
        Type testBaseType = c;
        while (testBaseType != null) {
            if (baseType.GetFullName(strongName) == testBaseType.GetFullName(strongName)) return true;
            testBaseType = testBaseType.BaseType;
        }
        return false;
    }

    public static bool ImplementsOpenInterface(this Type sourceType, Type ifaceType, bool strongName = true) {
        if (!ifaceType.IsInterface) return false;
        return sourceType.GetInterfaces().Any(I => I.GetFullName(strongName) == ifaceType.GetFullName(strongName));
    }

    public static string GetFullName(this Type type, bool strongName = false) {
        string name = type.FullName ?? "";
        if (name.Length == 0)
            name = type.Namespace + "." + type.Name;
        if (strongName)
            name += ", " + type.Assembly.FullName;
        return name;
    }
}

次のオープン ジェネリック インターフェイスがあるとします。

namespace TypeExample {
    public interface IBase<T> { }
    public interface IDerived<T> : IBase<T> { }
    public interface IDerived2<T> : IDerived<T> { }

    public class Base<T> : IBase<T> { }
    public class Derived<T> : Base<T>, IDerived<T> { }
    public class Derived2<T> : Derived<T>, IDerived2<T> { }
}

以下のすべてが返されtrueます:

typeof(IBase<>).OpenIsAssignableFrom(typeof(Base<>));
typeof(IBase<>).OpenIsAssignableFrom(typeof(Derived2<>));
typeof(Base<>).OpenIsAssignableFrom(typeof(Derived2<>));
typeof(IBase<>).OpenIsAssignableFrom(typeof(IDerived2<>));

これは、構築されたジェネリック型と組み込みの IsAssignableFrom を使用して、次のように直観的に同一の結果になります。

typeof(IBase<string>).IsAssignableFrom(typeof(Base<string>));
typeof(IBase<string>).IsAssignableFrom(typeof(Derived2<string>));
typeof(Base<string>).IsAssignableFrom(typeof(Derived2<string>));
typeof(IBase<string>).IsAssignableFrom(typeof(IDerived2<string>));
于 2012-08-06T14:31:24.530 に答える