2008-08-25 23 views
416

Mit Reflektion, wie kann ich alle Typen, die eine Schnittstelle mit C# 3.0/.NET 3.5 implementieren, mit dem geringsten Code und minimierten Iterationen erhalten?Abrufen aller Typen, die eine Schnittstelle implementieren

Dies ist, was ich neu zu schreiben wollen:

foreach (Type t in this.GetType().Assembly.GetTypes()) 
    if (t is IMyInterface) 
     ; //do stuff 
+0

Funktioniert der Beispielcode?Ich habe falsche Negative mit Ihrer if-Bedingung. –

+2

Die if-Anweisung im obigen Code wird immer falsch sein, weil Sie testen, ob eine Instanz der Type-Klasse (t) Ihre Schnittstelle implementiert. Dies geschieht nur, wenn Type IMyInterface erbt (in diesem Fall wird es immer wahr). – Liazy

Antwort

617

-Mine würde dies in seine C# 3.0 :)

var type = typeof(IMyInterface); 
var types = AppDomain.CurrentDomain.GetAssemblies() 
    .SelectMany(s => s.GetTypes()) 
    .Where(p => type.IsAssignableFrom(p)); 

Grundsätzlich wird die geringste Menge an Iterationen immer sein:

loop assemblies 
loop types 
    see if implemented. 
+138

Beachten Sie, dass die Liste auch die Schnittstelle selbst enthalten kann. Ändern Sie die letzte Zeile in '.Where (p => type.IsAssignableFrom (p) &&! P.IsInterface);' um sie herauszufiltern (oder 'p.IsClass'). – jtpereyda

+23

Hinweis: Diese Antwort ist falsch !, überprüft dies "Zuordnung Kompatibilität" nicht, ob die Schnittstelle implementiert ist nicht. Zum Beispiel implementiert 'List ' 'IEnumerable ' nicht, aber diese Methode wird True in .Net 4.0 aufgrund der Kovarianz zurückgeben, die tatsächlich falsch ist. [Richtige Antwort ist hier] (http://stackoverflow.com/a/12602220/2530848) –

+9

@SriramSakthivel erstens, generische Werte wurden nicht angegeben. Zweitens liegt diese Frage vor der Kovarianz. Drittens machen Sie die Annahme, dass kovariante Rückkehr nicht etwas ist, was sie wollen. –

11

Schleife durch alle geladenen Baugruppen, eine Schleife durch alle ihre Typen, und überprüfen, ob sie die Schnittstelle implementieren.

so etwas wie:

Type ti = typeof(IYourInterface); 
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { 
    foreach (Type t in asm.GetTypes()) { 
     if (ti.IsAssignableFrom(t)) { 
      // here's your type in t 
     } 
    } 
} 
4

Edit: Ich habe gerade den Schnitt gesehen, um zu verdeutlichen, dass die ursprüngliche Frage für die Reduzierung von Iterationen/Code war und das alles gut ist l und gut als Übung, aber in realen Situationen wollen Sie die schnellste Implementierung, unabhängig davon, wie cool das zugrunde liegende LINQ aussieht.

Hier ist meine Utils-Methode zum Iterieren der geladenen Typen. Es behandelt sowohl reguläre Klassen als auch Interfaces, und die Option excludeSystemTypes beschleunigt enorm, wenn Sie nach Implementierungen in Ihrer eigenen Codebasis von Drittanbietern suchen.

public static List<Type> GetSubclassesOf(this Type type, bool excludeSystemTypes) { 
    List<Type> list = new List<Type>(); 
    IEnumerator enumerator = Thread.GetDomain().GetAssemblies().GetEnumerator(); 
    while (enumerator.MoveNext()) { 
     try { 
      Type[] types = ((Assembly) enumerator.Current).GetTypes(); 
      if (!excludeSystemTypes || (excludeSystemTypes && !((Assembly) enumerator.Current).FullName.StartsWith("System."))) { 
       IEnumerator enumerator2 = types.GetEnumerator(); 
       while (enumerator2.MoveNext()) { 
        Type current = (Type) enumerator2.Current; 
        if (type.IsInterface) { 
         if (current.GetInterface(type.FullName) != null) { 
          list.Add(current); 
         } 
        } else if (current.IsSubclassOf(type)) { 
         list.Add(current); 
        } 
       } 
      } 
     } catch { 
     } 
    } 
    return list; 
} 

Es ist nicht schön, ich gebe es zu.

+1

Enumerators implementieren IDisposable, das nicht in try/finally abgelegt wird. Es ist besser, eine Foreach oder Linq zu verwenden. – TamusJRoyce

2

Es gibt keinen einfachen Weg (in Bezug auf die Leistung) zu tun, was Sie tun möchten.

Reflection arbeitet hauptsächlich mit Assemblys und Typen, so dass Sie alle Typen der Assembly abrufen und diese für die richtige Schnittstelle abfragen müssen. Hier ein Beispiel:

Assembly asm = Assembly.Load("MyAssembly"); 
Type[] types = asm.GetTypes(); 
Type[] result = types.where(x => x.GetInterface("IMyInterface") != null); 

, dass Sie alle Arten erhalten, die die IMyInterface in der Versammlung implementieren MyAssembly

-3

Sie einige LINQ nutzen, um die Liste zu bekommen:

var types = from type in this.GetType().Assembly.GetTypes() 
      where type is ISomeInterface 
      select type; 

Aber wirklich, ist das lesbarer?

+5

Es könnte besser lesbar sein, wenn es funktioniert. Leider überprüft Ihre where-Klausel, ob eine Instanz der System.Type-Klasse ISomeInterface implementiert, die niemals wahr sein wird, es sei denn, ISomeInterface ist wirklich IReflect oder ICustomAttributeProvider. In diesem Fall ist es immer wahr. –

+0

Carl Nayak Antwort oben hat die Antwort auf die where-Klausel zu korrigieren: IsAssignableFrom. Einfacher Fehler für eine Antwort. – TamusJRoyce

49

Um alle Typen in einer Assembly zu finden, die IFoo Schnittstelle implementieren:

var results = from type in someAssembly.GetTypes() 
       where typeof(IFoo).IsAssignableFrom(type) 
       select type; 

Beachten Sie, dass Ryan Rinaldi Vorschlag falsch war. Es gibt 0 Arten zurück. Sie können nicht schreiben

where type is IFoo 

, da Typ eine System.Type-Instanz ist und nie vom Typ IFoo sein wird. Stattdessen überprüfen Sie, ob IFoo vom Typ zuweisbar ist. Das wird deine erwarteten Ergebnisse bringen.

Auch Adam Wrights Vorschlag, der derzeit als Antwort markiert ist, ist ebenfalls falsch und aus dem gleichen Grund. Zur Laufzeit werden 0 Typen zurückgegeben, da alle System.Type-Instanzen keine IFoo-Implementierungen waren.

6

Das funktionierte für mich (wenn Sie wünschen, Systemtypen bei der Suche ausschließen können): hier

Type lookupType = typeof (IMenuItem); 
IEnumerable<Type> lookupTypes = GetType().Assembly.GetTypes().Where(
     t => lookupType.IsAssignableFrom(t) && !t.IsInterface); 
16

Andere Antworten IsAssignableFrom verwenden. Sie können auch FindInterfaces aus dem Namespace System verwenden, wie beschrieben here.

Hier ist ein Beispiel, das alle Assemblys im Ordner der gerade ausgeführten Assembly überprüft und nach Klassen sucht, die eine bestimmte Schnittstelle implementieren (LINQ wird aus Gründen der Übersichtlichkeit vermieden).

static void Main() { 
    const string qualifiedInterfaceName = "Interfaces.IMyInterface"; 
    var interfaceFilter = new TypeFilter(InterfaceFilter); 
    var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 
    var di = new DirectoryInfo(path); 
    foreach (var file in di.GetFiles("*.dll")) { 
     try { 
      var nextAssembly = Assembly.ReflectionOnlyLoadFrom(file.FullName); 
      foreach (var type in nextAssembly.GetTypes()) { 
       var myInterfaces = type.FindInterfaces(interfaceFilter, qualifiedInterfaceName); 
       if (myInterfaces.Length > 0) { 
        // This class implements the interface 
       } 
      } 
     } catch (BadImageFormatException) { 
      // Not a .net assembly - ignore 
     } 
    } 
} 

public static bool InterfaceFilter(Type typeObj, Object criteriaObj) { 
    return typeObj.ToString() == criteriaObj.ToString(); 
} 

Sie können eine Liste von Schnittstellen einrichten, wenn Sie mehr als eine Schnittstelle erstellen möchten.

+0

Dieser sucht nach String-Interface-Namen, wonach ich gesucht habe. – senthil

+0

Funktioniert beim Laden einer Assembly in einer anderen Domäne, da der Typ in eine Zeichenfolge serialisiert werden muss. sehr beeindruckend! – TamusJRoyce

40

Das funktionierte für mich. Es Schleifen obwohl die Klassen und überprüft, ob sie von myInterface

foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes() 
       .Where(mytype => mytype .GetInterfaces().Contains(typeof(myInterface)))) { 
    //do stuff 
} 
+1

Sie gehen davon aus, dass sich die Assembly in der Hauptdatei befindet. Kein zusätzliches Projekt. Sie durchlaufen auch unnötig viele Iterationen. Es ist besser, wenn der Rahmen schwer belastet wird. Dann weiter filtern, wenn gefunden. Falls relevant, aktualisieren Sie Ihre Antwort. Liste einschließen Begründung. var classTypesImplementingInterface = AppDomain.CurrentDomain.GetAssemblies(). SelectMany (x => x.GetTypes()) .Where (mytype => typeof (meinInterface) .IsAssignableFrom (meinTyp) && meinTyp.GetInterfaces(). Enthält (typeof (meinInterface))); foreach (var Element in Elementen) Console.Log (Element.Name); – TamusJRoyce

35

schließt man I schätzen dies eine sehr alte Frage, aber ich dachte, dass ich eine andere Antwort für zukünftige Benutzer, da alle Antworten auf Datum hinzufügen würde in irgendeiner Form verwenden von Assembly.GetTypes.

Während GetTypes() in der Tat alle Typen zurückgibt, bedeutet es nicht unbedingt, dass Sie sie aktivieren könnten und somit möglicherweise eine ReflectionTypeLoadException werfen könnten.

Ein klassisches Beispiel für keinen Typen zu aktivieren in der Lage wäre, wenn die zurück Typ derived von base aber base ist in einer anderen Baugruppe von den derived, eine Baugruppe definiert, dass die anrufende Assembly verweist nicht.

So sagen wir:

Class A // in AssemblyA 
Class B : Class A, IMyInterface // in AssemblyB 
Class C // in AssemblyC which references AssemblyB but not AssemblyA 

Wenn in ClassC, die in AssemblyC ist, dass wir dann etwas tun, wie pro akzeptierte Antwort:

var type = typeof(IMyInterface); 
var types = AppDomain.CurrentDomain.GetAssemblies() 
    .SelectMany(s => s.GetTypes()) 
    .Where(p => type.IsAssignableFrom(p)); 

Dann wird es einen ReflectionTypeLoadException werfen.

Dies liegt daran, ohne Verweis auf AssemblyA in AssemblyC würden Sie nicht in der Lage sein:

var bType = typeof(ClassB); 
var bClass = (ClassB)Activator.CreateInstance(bType); 

Mit anderen Worten ClassB ist nicht ladbaren die etwas, das der Aufruf von GetTypes Kontrollen und wirft.

Also, um sicher die Ergebnismenge für ladbare Typen zu qualifizieren dann nach dieser Phil Haacked Artikel Get All Types in an Assembly und Jon Skeet code Sie stattdessen etwas tun würde, wie:

public static class TypeLoaderExtensions { 
    public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) { 
     if (assembly == null) throw new ArgumentNullException("assembly"); 
     try { 
      return assembly.GetTypes(); 
     } catch (ReflectionTypeLoadException e) { 
      return e.Types.Where(t => t != null); 
     } 
    } 
} 

Und dann:

private IEnumerable<Type> GetTypesWithInterface(Assembly asm) { 
    var it = typeof (IMyInterface); 
    return asm.GetLoadableTypes().Where(it.IsAssignableFrom).ToList(); 
} 
+3

Dies half mir, mit einem super seltsamen Problem fertig zu werden, wo in meinem Testprojekt GetTypes scheitern würde und nur in unserer CI-Umgebung. GetLoadableTypes war ein Fix für diese Lösung. Der Fehler wäre in der lokalen Umgebung nicht reproduzierbar und es war dies: System.Reflection.ReflectionTypeLoadException: Kann einen oder mehrere der angeforderten Typen nicht laden. Rufen Sie die LoaderExceptions-Eigenschaft für weitere Informationen auf. Genauer gesagt beklagte es sich, dass es einen Typ gab, der keine konkrete Implementierung hatte und im Unit-Test-Projekt passierte. Danke dafür! –

+1

Diese Antwort sollte als Lösung markiert werden, es rettete meinen Arsch heute, weil wie @Lari Tuomisto sagte, auf lokaler Env konnten wir ähnliche Fehler nicht erneut produzieren – Lightning3

+1

Falls es jemand anderem hilft: diese Lösung funktionierte für mich, aber ich musste es ändern, um den Schnittstellentyp aus der Liste zu entfernen. Ich wollte 'CreateInstance' für alle aktivieren, und es wurde eine Ausnahme ausgelöst, als ich versuchte, die eigentliche Schnittstelle zu erstellen (was mich einige Zeit verwirrt hatte, als ich dachte, dass die eigentliche Schnittstelle in dieser Lösung nicht im Weg war). Also habe ich den Code geändert in 'GetLoadableTypes (Assembly) .Where (interfaceType.IsAssignableFrom) .Where (t =>! (T.Equals (interfaceType))). ToList();'. –

0

I Ausnahmen bekam im linq-code mache ich es so (ohne eine komplizierte erweiterung):

private static IList<Type> loadAllTypes(Types[] interfaces) 
{ 
    IList<Type> objects = new List<Type>(); 

    // find all types 
    foreach (var interfaceType in interfaces) 
     foreach (var currentAsm in AppDomain.CurrentDomain.GetAssemblies()) 
      try 
      { 
       foreach (var currentType in currentAsm.GetTypes()) 
        if (interfaceType.IsAssignableFrom(currentType) && currentType.IsClass && !currentType.IsAbstract) 
         objects.Add(currentType); 
      } 
      catch { } 

    return objects; 
} 
Verwandte Themen