別のジェネリックから直接継承するジェネリック インターフェイスの例は、オープン ジェネリック間の互換性を解決する際の複雑さを覆い隠しています。詳細については、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
プロパティを持つという事実に由来します。したがって、完全なアセンブリ名を含めるかどうかを決定するオプションのパラメーターを使用して、独自の拡張メソッドによっても定義します。Namespace
Name
GetFullName()
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>));