So bestimmen Sie, ob ein Typ einen bestimmten generischen Schnittstellentyp implementiert

226

Nehmen Sie die folgenden Typdefinitionen an:

public interface IFoo<T> : IBar<T> {}
public class Foo<T> : IFoo<T> {}

Wie finde ich heraus, ob der Typ Foodie generische Schnittstelle implementiert, IBar<T>wenn nur der verstümmelte Typ verfügbar ist?

sduplooy
quelle

Antworten:

387

Mit der Antwort von TcKs kann dies auch mit der folgenden LINQ-Abfrage erfolgen:

bool isBar = foo.GetType().GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IBar<>));
sduplooy
quelle
1
Dies ist eine sehr elegante Lösung! Die anderen, die ich auf SO gesehen habe, verwenden foreach-Schleifen oder längere LINQ-Abfragen. Beachten Sie jedoch, dass Sie dazu .NET Framework 3.5 benötigen.
Daniel T.
7
Ich empfehle Ihnen, dies zu einer Erweiterungsmethode a la bit.ly/ccza8B zu machen - wird dies ganz schön bereinigen!
Brad Heller
1
Abhängig von Ihren Anforderungen müssen Sie möglicherweise die zurückgegebenen Schnittstellen erneut verwenden.
Sebastian Good
4
Ich würde sagen, dies hätte viel besser in .net implementiert werden sollen ... als Kern ... wie member.Implements (IBar) oder CustomType.Implements (IBar) oder noch besser mit einem Schlüsselwort "is". .. Ich erkunde c # und ich bin ein bisschen mehr als ein bisschen enttäuscht von .net im Moment ...
Sofija
2
geringfügiger Zusatz: Wenn IBar mehrere generische Typen hat, müssen Sie dies wie folgt angeben: typeof(IBar<,,,>)mit Kommas, die wie Platzhalter wirken
Rob Von Nesselrode
33

Sie müssen den Vererbungsbaum durchgehen und alle Schnittstellen für jede Klasse im Baum finden und typeof(IBar<>)mit dem Ergebnis des Aufrufs vergleichen , Type.GetGenericTypeDefinition wenn die Schnittstelle generisch ist. Es ist sicherlich alles ein bisschen schmerzhaft.

Weitere Informationen und Code finden Sie in dieser und diesen Antworten .

Jon Skeet
quelle
Warum nicht einfach in IBar <SomeClass> umwandeln und nach Null suchen? (Ich meine natürlich Casting mit 'as')
Pablo Retyk
5
T ist unbekannt und kann nicht auf einen bestimmten Typ umgestellt werden.
SDUPLOO
@sduplooy: Vielleicht fehlt mir etwas, wie kann T unbekannt sein? es würde öffentliche Klasse Foo kompilieren: IFoo <T> {}
Pablo Retyk
25
public interface IFoo<T> : IBar<T> {}
public class Foo : IFoo<Foo> {}

var implementedInterfaces = typeof( Foo ).GetInterfaces();
foreach( var interfaceType in implementedInterfaces ) {
    if ( false == interfaceType.IsGeneric ) { continue; }
    var genericType = interfaceType.GetGenericTypeDefinition();
    if ( genericType == typeof( IFoo<> ) ) {
        // do something !
        break;
    }
}
TcKs
quelle
2
Da typeof (Foo) ein System.Type-Objekt zurückgibt (das Foo beschreibt), gibt der Aufruf GetType () immer den Typ für System.Type zurück. Sie sollten zu typeof (Foo) wechseln. GetInterfaces ()
Michael Meadows
9

Als Hilfsmethodenerweiterung

public static bool Implements<I>(this Type type, I @interface) where I : class
{
    if(((@interface as Type)==null) || !(@interface as Type).IsInterface)
        throw new ArgumentException("Only interfaces can be 'implemented'.");

    return (@interface as Type).IsAssignableFrom(type);
}

Anwendungsbeispiel:

var testObject = new Dictionary<int, object>();
result = testObject.GetType().Implements(typeof(IDictionary<int, object>)); // true!
GenericProgrammer
quelle
2
"IsAssignableFrom" war genau das, wonach ich gesucht hatte - danke
Jesper
22
Dies funktioniert nicht für die Anforderung des Fragestellers, den generischen Typparameter nicht zu kennen. Aus Ihrem Beispiel testObject.GetType (). Implements (typeof (IDictionary <,>)); wird false zurückgeben.
Ctusch
@ctusch dann eine Lösung dafür?
Tohid
5

Ich verwende eine etwas einfachere Version der @ GenericProgrammers-Erweiterungsmethode:

public static bool Implements<TInterface>(this Type type) where TInterface : class {
    var interfaceType = typeof(TInterface);

    if (!interfaceType.IsInterface)
        throw new InvalidOperationException("Only interfaces can be implemented.");

    return (interfaceType.IsAssignableFrom(type));
}

Verwendung:

    if (!featureType.Implements<IFeature>())
        throw new InvalidCastException();
Ben Foster
quelle
5
Es funktioniert immer noch nicht gemäß der Anforderung der ursprünglichen Frage, die für generische Schnittstellen gilt.
Nathanchere
4

Sie müssen gegen einen konstruierten Typ der generischen Schnittstelle prüfen.

Sie müssen so etwas tun:

foo is IBar<String>

weil IBar<String>repräsentiert diesen konstruierten Typ. Der Grund, warum Sie dies tun müssen, ist T, dass der Compiler nicht weiß, ob Sie IBar<Int32>oder meinen, wenn dies in Ihrer Prüfung nicht definiert ist IBar<SomethingElse>.

Andrew Hare
quelle
4

Um den Typ System vollständig zu bewältigen, ich glaube , Sie Griff Rekursion benötigen, zB IList<T>: ICollection<T>: IEnumerable<T>, ohne die Sie nicht wissen , dass IList<int>letztlich implementiert IEnumerable<>.

    /// <summary>Determines whether a type, like IList&lt;int&gt;, implements an open generic interface, like
    /// IEnumerable&lt;&gt;. Note that this only checks against *interfaces*.</summary>
    /// <param name="candidateType">The type to check.</param>
    /// <param name="openGenericInterfaceType">The open generic type which it may impelement</param>
    /// <returns>Whether the candidate type implements the open interface.</returns>
    public static bool ImplementsOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType)
    {
        Contract.Requires(candidateType != null);
        Contract.Requires(openGenericInterfaceType != null);

        return
            candidateType.Equals(openGenericInterfaceType) ||
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition().Equals(openGenericInterfaceType)) ||
            candidateType.GetInterfaces().Any(i => i.IsGenericType && i.ImplementsOpenGenericInterface(openGenericInterfaceType));

    }
Sebastian Gut
quelle
3

Zunächst public class Foo : IFoo<T> {}wird nicht kompiliert, da Sie anstelle von T eine Klasse angeben müssen, sondern davon ausgehen, dass Sie so etwas tunpublic class Foo : IFoo<SomeClass> {}

dann, wenn du es tust

Foo f = new Foo();
IBar<SomeClass> b = f as IBar<SomeClass>;

if(b != null)  //derives from IBar<>
    Blabla();
Pablo Retyk
quelle
2

Für den Fall, dass Sie eine Erweiterungsmethode wünschen, die sowohl generische Basistypen als auch Schnittstellen unterstützt, habe ich die Antwort von sduplooy erweitert:

    public static bool InheritsFrom(this Type t1, Type t2)
    {
        if (null == t1 || null == t2)
            return false;

        if (null != t1.BaseType &&
            t1.BaseType.IsGenericType &&
            t1.BaseType.GetGenericTypeDefinition() == t2)
        {
            return true;
        }

        if (InheritsFrom(t1.BaseType, t2))
            return true;

        return
            (t2.IsAssignableFrom(t1) && t1 != t2)
            ||
            t1.GetInterfaces().Any(x =>
              x.IsGenericType &&
              x.GetGenericTypeDefinition() == t2);
    }
Philip Pittle
quelle
1

Methode zum Überprüfen, ob der Typ einen generischen Typ erbt oder implementiert:

   public static bool IsTheGenericType(this Type candidateType, Type genericType)
    {
        return
            candidateType != null && genericType != null &&
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == genericType ||
             candidateType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericType) ||
             candidateType.BaseType != null && candidateType.BaseType.IsTheGenericType(genericType));
    }
Derek Greer
quelle
0

Versuchen Sie die folgende Erweiterung.

public static bool Implements(this Type @this, Type @interface)
{
    if (@this == null || @interface == null) return false;
    return @interface.GenericTypeArguments.Length>0
        ? @interface.IsAssignableFrom(@this)
        : @this.GetInterfaces().Any(c => c.Name == @interface.Name);
}

Um es zu testen. erstellen

public interface IFoo { }
public interface IFoo<T> : IFoo { }
public interface IFoo<T, M> : IFoo<T> { }
public class Foo : IFoo { }
public class Foo<T> : IFoo { }
public class Foo<T, M> : IFoo<T> { }
public class FooInt : IFoo<int> { }
public class FooStringInt : IFoo<string, int> { }
public class Foo2 : Foo { }

und die Testmethode

public void Test()
{
    Console.WriteLine(typeof(Foo).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<int>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<string>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<string,int>)));
    Console.WriteLine(typeof(Foo<int,string>).Implements(typeof(IFoo<string>)));
 }
Waleed AK
quelle
-1

Folgendes sollte nicht falsch sein:

bool implementsGeneric = (anObject.Implements("IBar`1") != null);

Als zusätzliche Gutschrift können Sie AmbiguousMatchException abfangen, wenn Sie Ihrer IBar-Abfrage einen bestimmten generischen Typparameter bereitstellen möchten.

mindlace
quelle
Normalerweise ist es besser, wenn möglich keine String-Literale zu verwenden. Dieser Ansatz würde es schwieriger machen, die Anwendung umzugestalten, da das Umbenennen der IBar-Schnittstelle das Zeichenfolgenliteral nicht ändern würde und der Fehler nur zur Laufzeit erkennbar wäre.
andyroschy
So sehr ich dem obigen Kommentar zur Verwendung von "magischen Saiten" usw. normalerweise zustimme, ist dies immer noch der beste Ansatz, den ich gefunden habe. Gut genug - Testen auf den PropertyType.Name gleich "IWhatever`1".
Nathanchere
Warum nicht das? bool implementsGeneric = (anObject.Implements(typeof(IBar<>).Name) != null);
Maxime Gélinas