8

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

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

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

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

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

如果我在另一个 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 容器注册的类型的用法(您可以在此处观看有关其工作原理的简短视频)。

在大多数情况下,分析容器注册很简单——我只需要检测足够的信息就可以知道哪些具体类型受到影响。在这个温莎城堡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());

来源

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

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

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

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

感谢您的阅读。此问题可用时将获得 500 点奖励。

4

2 回答 2

2

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

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

由于各种原因,这种要求变得越来越频繁。我在看这个是因为我想通过 WCF 服务将 Linq 喷射到实体表达式。

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

我找到了这个:CodePlex - 表达式树序列化器


我刚刚用 测试了它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
于 2012-05-07T11:50:35.393 回答
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);
  }
}
于 2012-05-07T13:20:02.093 回答