2012-05-07 58 views
8

将其类型谓词请阅读整个问题。我有一个独特的情况,有几个我想解决的限制。加载一个组件,而在另一个应用程序域

在我的代码有被编译成Predicate<System.Type>表达式树。我的目标是加载程序集没有锁定它(这是该项目的输出组件,被不断地重修),适用其类型的列表在这个谓语并获得结果类型的回列表

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

该程序集应该加载到另一个应用程序域中,因为我希望一收到我需要的信息就立即卸载它。

这是它得到棘手。有几个我正面临的问题:

如果我在另一个appdomain中加载程序集并简单地返回它的所有类型的数组,以便我可以将谓词应用回到我的主appdomain中,只要类型被编组回到我的主要应用程序域我得到一个FileNotFoundException,指出该组件是找不到的。这使得sence,因为程序集只被加载到我创建的另一个appdomain中。在主应用程序域中加载它也会失败。

如果或者我尝试将谓词传递给另一个appdomain并将其应用于其中并返回一个字符串数组(完整类型名称),我得到一个SerializationException: "Cannot serialize delegates over unmanaged function pointers, dynamic methods or methods outside the delegate creator's assembly.",因为谓词是一个动态方法(已编译从表达式树中)。

将其加载到主应用程序域中将不会有这些问题,但由于无法卸载已加载的程序集而无需卸载整个应用程序域,只要程序集发生更改(重建后),任何尝试加载程序集具有相同的名称将导致例外。

语境:
我建立ReSharper的一个插件叫做Agent Mulder。插件背后的想法是分析解决方案中的DI/IoC容器注册,并帮助ReSharper找出通过DI容器注册的类型的使用情况(您可以观看它的工作原理的简短视频here)。

大多数情况下,分析容器注册非常简单 - 我只需检测足够的信息即可知道哪些具体类型受到影响。在Castle Windsor的这个例子中:Component.For<IFoo>().ImplementedBy<Foo>()由此产生的类型是显而易见的,AllTypes.FromThisAssembly().BasedOn<IFoo>()也是如此 - 给我提供足够的信息来对这条线会影响到的具体类型提供猜测。然而,考虑这个注册在温莎城堡:

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

source

这里的信息取决于只会在运行时进行评估的谓词。

因为我所能做的就是静态分析这一点,所以我手里拿着来自Where子句的lambda表达式的ReSharper AST(在ReSharper中称为PSI)表示。我可以将这个AST转换成一个LINQ表达式树,然后将它编译成一个委托。

我的想法是是加载通过反射输出组件(由FromAssembly*描述符确定),并且为了获得类型名(我只需要姓名)在组装的类型应用此委托。每次装配改变时都必须重新评估(我不关心性能的这一点)。总之,除非有人能够推荐一种更好的方式来确定受谓词影响的类型,我想知道如何用反射来做到这一点(不幸的是我没有考虑过其他元数据读者,因为我会必须以某种方式将lambda表达式AST转换为不同数据类型的谓词,并且我不知道是否存在1对1转换)。

谢谢您的阅读。这个问题在可用时将有500点奖励。

+0

如果'Expression'是序列化,你可以创建'Predicate'委托作为一个'Expression',并将其传递给appdomain边界,然后在'remote'appdomain中进行编译。但可悲的是,那不是那种情况:( – leppie

+0

@leppie是的,我想过序列化表达式并传递它,你是对的,它并不像听起来那么简单... –

回答

2

您需要加载程序集才能获得Type实例,因此单独的AppDomain似乎是正确的解决方案。

因此,您需要将谓词Expression转换为AppDomain,这意味着您必须对其进行序列化/反序列化。

由于各种原因,此要求越来越频繁。我在看这个,因为我想通过WCF服务将Linq转换为Entities表达式。

幸运的是,有一些现有的实现。

我发现这一个:CodePlex - Expression Tree Serializer


我刚刚与Types测试它,这一点也适用:

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

谢谢!经过额外的杂技(XElement本身并没有标记'[Serializable]',这就打破了appdomains之间的转换),我通过转换/从一个字符串来解决它。对于简单的表达式,这*看起来*做这项工作 - 我将不得不尝试更复杂的。如果这个工程,你的答案可能是:) –

+0

不客气。那个图书馆为我节省了很多工作。如果您发现任何它无法处理的情况,请让我知道。 –

1

让包裹谓词委托实例与MBR对象,Type类型的参数将从其他领域编组得很好:

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

建立某种类型的加载,探索组装并将结果返回给主域名:

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

让所有的工作:

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); 
    } 
} 
相关问题