加载程序集并在另一个应用程序域中对其类型应用谓词

Posted

技术标签:

【中文标题】加载程序集并在另一个应用程序域中对其类型应用谓词【英文标题】:Loading an assembly and applying a predicate on its types in another appdomain 【发布时间】:2012-05-07 11:15:32 【问题描述】:

请阅读整个问题。我有一个独特的情况,有几个我想解决的限制。

在我的代码中,我有一个表达式树,它被编译为Predicate<System.Type>。我的目标是加载一个程序集而不锁定它(它是项目的输出程序集,不断重建),将此谓词应用于其类型列表并返回结果类型 names

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

这个程序集应该加载到另一个应用程序域中,因为我想在获得所需信息后立即卸载它。

这就是棘手的地方。我面临几个问题:

如果我在另一个 appdomain 中加载程序集并简单地返回一个包含其所有类型的数组,以便我可以将谓词应用回我的主 appdomain,只要将类型编组回我的主 appdomain,我就会得到一个FileNotFoundException,表示找不到此程序集。这是有道理的,因为程序集只被加载到我创建的另一个 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)。

在大多数情况下,分析容器注册很简单——我只需要检测足够的信息就可以知道哪些具体类型受到影响。在这个温莎城堡的例子中:Component.For<IFoo>().ImplementedBy<Foo>() 得到的类型很明显,AllTypes.FromThisAssembly().BasedOn<IFoo>() 也很明显 - 给我足够的信息来推测受这条线影响的具体类型。但是,请考虑在 Castle 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)

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

由于我所能做的就是静态分析它,我手中有来自Where 子句的 lambda 表达式的 ReSharper AST(在 ReSharper 中称为 PSI)表示。我可以将此 AST 转换为 LINQ 表达式树,然后将其编译为委托。

我的想法是通过反射加载输出程序集(由FromAssembly* 描述符确定),并将此委托应用于程序集的类型以获得类型名称(我只需要名称)。每次组件更改时都必须重新评估这一点(我现在不关心性能)。

总之,除非有人可以推荐一种更好的方法来确定受谓词影响的类型,否则我想知道如何通过反射来做到这一点(不幸的是,我没有考虑其他元数据阅读器,因为我有以某种方式将 lambda 表达式 AST 转换为不同数据类型的谓词,我不知道是否存在 1 对 1 的转换)。

感谢您的阅读。此问题可用时将有 500 分奖励。

【问题讨论】:

如果Expression 是可序列化的,您可以将Predicate 委托创建为Expression,并将其传递到应用域边界,然后在“远程”应用域中编译它。但可悲的是,情况并非如此:( @leppie 是的,我想过序列化表达式并传递它,你是对的,它不像听起来那么简单...... 【参考方案1】:

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

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

由于各种原因,此要求变得越来越频繁。我在看这个是因为我想在 WCF 服务中使用 Linq to 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

【讨论】:

谢谢!经过额外的杂技(XElement 本身没有标记[Serializable],这会破坏appdomains 之间的传输),我通过转换到/从字符串来解决它。对于简单的表达式,这 似乎 可以完成工作——我将不得不尝试使用更复杂的表达式。如果这行得通,你的答案可能就是它:) 不客气。那个图书馆为我节省了很多工作。如果您发现任何它无法处理的情况,请告诉我。【参考方案2】:

让我们用 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);
  

【讨论】:

以上是关于加载程序集并在另一个应用程序域中对其类型应用谓词的主要内容,如果未能解决你的问题,请参考以下文章

创建应用程序域并加载程序集

从具有依赖关系的另一个文件夹加载另一个应用程序域中的程序集

如何在 .Net 中创建和解析标签、长度、值 (TLV) 并在 Base64 中对其进行编码

如何模板化一个接受模板化参数并在 C++ 中对其应用模板化函数的函数?

如何在解析后获取xml文件的元素并在iPhone程序中对其进行验证

我可以在运行时加载 .NET 程序集并实例化只知道名称的类型吗?