2008-08-25 15 views
416

Usando la reflexión, ¿cómo puedo obtener todos los tipos que implementan una interfaz con C# 3.0/.NET 3.5 con el menor código y minimizando las iteraciones?Obtención de todos los tipos que implementan una interfaz

Esto es lo que yo quiero volver a escribir:

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

¿Funciona el código de ejemplo?Tengo falsos negativos con tu condición if. –

+2

La sentencia if en el código anterior siempre será falsa porque está probando si una instancia de la clase Type (t) implementa su interfaz, lo que no sucederá a menos que Type herede IMyInterface (en cuyo caso siempre será verdadera). – Liazy

Respuesta

617

mío sería esto en C# 3.0 :)

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

Básicamente, habrá siempre la menor cantidad de iteraciones:

loop assemblies 
loop types 
    see if implemented. 
+138

Tenga en cuenta que la lista también puede incluir la interfaz en sí. Cambia la última línea a '.Where (p => type.IsAssignableFrom (p) &&! P.IsInterface);' para filtrarla (o 'p.IsClass'). – jtpereyda

+23

Nota: ¡Esta respuesta es incorrecta !, esto verifica "Compatibilidad de asignación" no si la interfaz está implementada no lo es. Por ejemplo, 'List ' no implementa 'IEnumerable ', pero este método devolverá true en .Net 4.0 debido a la covarianza, lo cual es incorrecto. [La respuesta correcta está aquí] (http://stackoverflow.com/a/12602220/2530848) –

+9

@SriramSakthivel primero, los valores genéricos no se especificaron. En segundo lugar, esta pregunta es anterior a la covarianza. En tercer lugar, asume que el retorno covariante no es algo que desean. –

11

bucle a través de todos los ensamblados cargados, recorrer todos sus tipos, y comprobar si implementan la interfaz.

algo como:

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

Edición: Acabo de ver la edición para aclarar que la pregunta original era para la reducción de iteraciones/código y eso es todo bien l y bueno como ejercicio, pero en situaciones del mundo real vas a querer la implementación más rápida, independientemente de lo atractivo que sea el LINQ subyacente.

Aquí está mi método Utils para recorrer los tipos cargados. Maneja clases regulares así como interfaces, y la opción excludeSystemTypes acelera enormemente las cosas si está buscando implementaciones en su propia base de código/de terceros.

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

No es bonito, lo admitiré.

+1

Los enumeradores implementan IDisposable que no se elimina en try/finally. Es mejor usar un foreach o linq. – TamusJRoyce

2

No hay una manera fácil (en términos de rendimiento) de hacer lo que quiere hacer.

La reflexión funciona principalmente con ensamblajes y tipos, por lo que deberá obtener todos los tipos de ensamblaje y consultarlos para la interfaz correcta. He aquí un ejemplo:

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

que les permite conocer todos los tipos que implementan la IMyInterface en la Asamblea MyAssembly

-3

Se podría utilizar un poco de LINQ para obtener la lista:

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

Pero, en realidad, es eso mas legible?

+5

Podría ser más legible, si funcionó. Desafortunadamente, su cláusula where está verificando si una instancia de la clase System.Type implementa ISomeInterface, que nunca será verdadera, a menos que ISomeInterface sea realmente IReflect o ICustomAttributeProvider, en cuyo caso siempre será verdadero. –

+0

La respuesta anterior de Carl Nayak tiene la respuesta para corregir la cláusula where: IsAssignableFrom. Fácil error por una respuesta. – TamusJRoyce

49

encontrar todo tipo en una asamblea que implementan la interfaz IFoo:

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

Tenga en cuenta que la sugerencia de Ryan Rinaldi era incorrecta. Devolverá 0 tipos. No puede escribir

where type is IFoo 

porque el tipo es una instancia de System.Type, y nunca será de tipo IFoo. En cambio, verifica si IFoo es asignable del tipo. Eso obtendrá los resultados esperados.

Además, la sugerencia de Adam Wright, que actualmente está marcada como la respuesta, también es incorrecta y por el mismo motivo. En el tiempo de ejecución, verá que regresan 0 tipos, porque todas las instancias System.Type no fueron implementadores IFoo.

6

Esto funcionó para mí (si lo desea, puede excluir tipos de sistemas en las operaciones de búsqueda):

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

Otras respuestas aquí utiliza IsAssignableFrom. También puede usar FindInterfaces desde el espacio de nombres System, como se describe here.

Aquí hay un ejemplo que comprueba todos los ensamblajes en la carpeta del ensamblaje que se está ejecutando actualmente, buscando clases que implementen una cierta interfaz (evitando LINQ para mayor claridad).

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

Puede configurar una lista de interfaces si desea hacer coincidir más de una.

+0

Este busca el nombre de la interfaz de cadena, que es lo que estaba buscando. – senthil

+0

Funciona cuando se carga un ensamblado en un dominio diferente, ya que el tipo tiene que ser serializado en una cadena. ¡Muy impresionante! – TamusJRoyce

40

Esto funcionó para mí. Se realiza un bucle, aunque las clases y comprueba si están derrived de MyInterface

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

Está asumiendo que el ensamblaje está en el ejecutable principal. No es un proyecto adicional. También está iterando innecesariamente a través de un montón de iteraciones. Es mejor que el marco haga el trabajo pesado. Luego filtre más abajo cuando se encuentre. Si es relevante, actualice su respuesta. Incluir el razonamiento de la lista . var classTypesImplementingInterface = AppDomain.CurrentDomain.GetAssemblies(). SelectMany (x => x.GetTypes()). Where (mytype => typeof (myInterface) .IsAssignableFrom (mytype) && mytype.GetInterfaces(). Contains (typeof (myInterface))); foreach (elemento var en elementos) Console.Log (item.Name); – TamusJRoyce

35

aprecio esta es una pregunta muy antigua pero pensé que me gustaría añadir otra respuesta para los futuros usuarios como todas las respuestas hasta la fecha usan alguna forma de Assembly.GetTypes.

Si bien GetTypes() devolverá todos los tipos, no significa necesariamente que pueda activarlos y, por lo tanto, podría arrojar un ReflectionTypeLoadException.

Un ejemplo clásico por no ser capaz de activar un tipo sería cuando el tipo devuelto es derived de base pero base se define en un conjunto diferente de la de derived, un conjunto que el conjunto de la llamada no referencia.

Así que decir que tenemos:

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

Si en ClassC que está en AssemblyC que luego hacer algo como respuesta aceptada por:

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

entonces será lanzar una ReflectionTypeLoadException.

Esto es porque sin una referencia a AssemblyA en AssemblyC que no sería capaz de:

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

En otras palabras ClassB no es cargable que es algo que la llamada a getTypes cheques y arroja sobre.

Así que para calificar de manera segura el conjunto de resultados para los tipos que se pueden cargar a continuación, según esta Phil Haacked artículo Get All Types in an Assembly y Jon Skeet code debe en su lugar hacer algo como:

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

Y luego:

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

Esto me ayudó a lidiar con un problema súper raro, donde en mi proyecto de prueba los GetTypes fallarían y solo en nuestro entorno de CI. GetLoadableTypes fue una solución para esta solución. El error no sería reproducible en el entorno local y fue este: System.Reflection.ReflectionTypeLoadException: no se puede cargar uno o más de los tipos solicitados. Recupere la propiedad LoaderExceptions para obtener más información. Más específicamente, se quejaba de que había un tipo que no tenía una implementación concreta y ocurría en el proyecto de prueba de unidad. ¡Gracias por esto! –

+1

Esta respuesta debe marcarse como solución, me salvó el culo hoy, porque al igual que @Lari Tuomisto dijo, en el entorno local no pudimos volver a producir un error similar – Lightning3

+1

En caso de que ayude a otra persona: esta solución funcionó para mí, pero tuvo que modificarlo para eliminar el tipo de interfaz de la lista. Quería activar 'CreateInstance' para todos ellos, y se lanzó una excepción cuando estaba tratando de crear la interfaz real (lo que me confundió por un tiempo cuando pensé que la interfaz real estaba fuera del camino en esta solución). Así que cambié el código a 'GetLoadableTypes (assembly) .Where (interfaceType.IsAssignableFrom) .Where (t =>! (T.Equals (interfaceType))). ToList();'. –

0

llegué excepciones en el código linq entonces lo hago de esta manera (sin una extensión complicada):

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