2012-05-07 10 views
8

Lea la pregunta completa. Tengo una situación única con varias limitaciones que me gustaría resolver.Cargando un ensamblado y aplicando un predicado sobre sus tipos en otro dominio de aplicación

En mi código tengo un árbol de expresiones que se compila en un Predicate<System.Type>. Mi objetivo es cargar un ensamblaje sin bloquearlo (es el ensamblaje de salida del proyecto, reconstruyéndolo constantemente), aplicar este predicado en la lista de sus tipos y obtener una lista del tipo resultante nombres:

// this is what I need: 
return assembly.GetTypes().Where(t => predicate(t)).Select(t => t.FullName); 

Este ensamblaje debe cargarse en otro dominio de aplicación, porque quiero descargarlo tan pronto como obtenga la información que necesito.

Aquí es donde se pone complicado. Hay varios problemas que estoy enfrentando:

Si cargo el ensamblado en otro dominio de aplicación y simplemente devuelvo una matriz de todos sus tipos, para poder aplicar el predicado de nuevo en mi dominio de aplicación principal, tan pronto como los tipos se agrupan de nuevo en mi dominio de aplicación principal obtengo un FileNotFoundException, indicando que no se encuentra este conjunto. Esto hace sentido, porque el ensamblaje solo se carga en otro dominio de aplicación que creé. Cargarlo también en el dominio de aplicación principal frustrará el propósito.

Si, alternativamente, trato de pasar el predicado al otro dominio de aplicación, para aplicarlo allí y obtener una matriz de cadenas (nombre de tipo completo), obtengo un SerializationException: "Cannot serialize delegates over unmanaged function pointers, dynamic methods or methods outside the delegate creator's assembly.", porque el predicado es un Método dinámico (compilado de un árbol de expresiones).

Al cargarlo en el appdomain primario no tendría ninguno de estos problemas, pero como es imposible descargar un ensamblado cargado sin descargar todo el dominio de aplicación, tan pronto como el ensamblaje cambie (después de la reconstrucción), cualquier intento de cargar un ensamblaje con el mismo nombre daría lugar a una excepción.

Contexto:
Estoy construyendo un plugin para ReSharper llama Agent Mulder. La idea detrás del complemento es analizar los registros DI/IoC Container en su solución y ayudar a ReSharper a descubrir el uso de los tipos registrados a través de un Contenedor DI (puede ver un video corto de cómo funciona here).

En su mayor parte, analizando el registro de contenedores es sencillo - sólo tengo para detectar suficiente información para saber qué tipos concretos se ven afectados. En este ejemplo, con el castillo de Windsor: Component.For<IFoo>().ImplementedBy<Foo>() el tipo resultante es obvio, por lo que es AllTypes.FromThisAssembly().BasedOn<IFoo>() - me da información suficiente para guestimate los tipos de hormigón que se verán afectados por esta línea. Sin embargo, tenga en cuenta este registro en el castillo de Windsor:

container.Register(Classes 
    .FromAssemblyInDirectory(new AssemblyFilter(".").FilterByName(an => an.Name.StartsWith("Ploeh.Samples.Booking"))) 
    .Where(t => !(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dispatcher<>))) 
    .WithServiceAllInterfaces()); 

(source)

Aquí la información depende de un predicado que sólo serán evaluados en tiempo de ejecución.

Como todo lo que puedo hacer es analizar estáticamente esto, tengo en mi mano la representación AST de ReSharper (llamada PSI en ReSharper) de la expresión lambda de la cláusula Where. Puedo convertir este AST en un árbol de expresiones LINQ, y luego compilarlo en un delegado.

Mi idea era cargar el ensamblaje de salida (determinado por el descriptor FromAssembly*) mediante reflexión, y aplicar este delegado en los tipos de ensamblaje para obtener los nombres de tipo (solo necesito los nombres). Esto tendrá que volver a evaluarse cada vez que cambie el ensamblaje también (no me preocupa en este punto el rendimiento).

En conclusión, a menos que alguien pueda recomendar una mejor manera de determinar los tipos afectados por el predicado, me gustaría saber cómo hacerlo con reflexión (lamentablemente no había considerado otros lectores de metadatos, porque me gustaría tiene que convertir de alguna manera la expresión lambda AST a un predicado de diferente tipo de datos, y no sé si existe una conversión 1-a-1).

Gracias por leer. Esta pregunta tendrá una recompensa de 500 puntos cuando esté disponible.

+0

Si 'Expression' era serializable, se puede crear la' delegado Predicate' como una 'Expresión', pasarla sobre el límite del dominio de la aplicación y compilarla en el dominio de aplicación 'remoto'. Pero lamentablemente, ese no es el caso :( – leppie

+0

@leppie Sí, había pensado en serializar la expresión y pasarla, tienes razón, no es tan simple como parece ... –

Respuesta

2

Debe cargar el conjunto para obtener las instancias Type, por lo que un AppDomain diferente parece ser la solución correcta.

Por lo tanto, debe obtener el predicado Expression en ese AppDomain, lo que significa que debe serializarlo/deserializarlo.

Este requisito es cada vez más frecuente por varias razones. Estaba viendo esto porque quería lanzar Linq a expresiones de Entidades a través de un servicio WCF.

Afortunadamente, hay algunas implementaciones existentes.

me encontré éste: CodePlex - Expression Tree Serializer


yo sólo lo he probado con Types, y esto funciona:

Expression<Func<Type,bool>> predicate = 
    t => (!t.IsGenericType && t.Name == "Int32"); 

var s = new ExpressionSerialization.ExpressionSerializer(); 
var xml = s.Serialize(predicate); 

var p = s.Deserialize(xml) as Expression<Func<Type, bool>>; 
var f = p.Compile(); 

Console.WriteLine("Int32: " + f(typeof(int))); // true 
Console.WriteLine("String: " + f(typeof(string))); // false 
+0

¡Gracias! Después de las acrobacias adicionales (XElement) sí mismo no está marcado como '[Serializable]', y esto interrumpe la transferencia entre aplicaciones), trabajé a su alrededor convirtiéndolo a/desde una cadena. Para expresiones simples esto * parece * hacer el trabajo - tendré que intentar con más complejos. Si esto funciona, tu respuesta podría ser :) :) –

+0

De nada. Esa biblioteca me ahorró mucho trabajo. Por favor, hágamelo saber si encuentra algún caso que no maneje. –

1

Lets envoltura predicado ejemplo delegado con objeto MBR, parámetro de tipo Type se calculará bien desde otro dominio:

class RemotablePredicate<T> : MarshalByRefObject 
{ 
    readonly Predicate<T> predicate; 
    public RemotablePredicate(Predicate<T> predicate) { this.predicate = predicate; } 
    public bool Accepts(T arg) { return predicate(arg); } 
} 

construir algún tipo de carga, montaje y explorar los resultados de volver al dominio principal:

class AssemblyExplorer : MarshalByRefObject 
{ 
    public string[] GetTypesByPredicate(
    string assemblyPath, RemotablePredicate<Type> predicate) 
    { 
    // MS reflection api reqires all dependencies here 
    var bytes = File.ReadAllBytes(assemblyPath); 
    var assembly = Assembly.ReflectionOnlyLoad(bytes); 

    var types = new List<string>(); 
    foreach (var type in assembly.GetTypes()) 
     if (predicate.Accepts(type)) 
     types.Add(type.FullName); 

    return types.ToArray(); 
    } 
} 

Hacer todo este trabajo:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq.Expressions; 
using System.Reflection; 

class Program 
{ 
    static void Main() 
    { 
    var fooDomain = AppDomain.CreateDomain("Foo"); 

    Expression<Predicate<Type>> expr = t => t.IsValueType; 
    var compiledPredicate = expr.Compile(); 
    var remotablePredicate = new RemotablePredicate<Type>(compiledPredicate); 

    var explorerType = typeof(AssemblyExplorer); 
    var explorerInstance = (AssemblyExplorer) fooDomain 
     .CreateInstanceAndUnwrap(explorerType.Assembly.FullName, explorerType.FullName); 

    var types = explorerInstance.GetTypesByPredicate(
     "JetBrains.Annotations.dll", remotablePredicate); 

    Console.WriteLine("Matched types: {0}", types.Length); 
    foreach (var type in types) Console.WriteLine(" {0}", type); 
    } 
} 
Cuestiones relacionadas