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?

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<>));

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 .


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;
    }
}

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!

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();

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>.


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));

    }

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();

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);
    }

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));
    }

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>)));
 }

-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.


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.