14

我创建了一个小应用程序,以递归方式在提供的目录中加载程序集并读取它们的自定义属性集合。主要只是读取 DebuggableAttribute 来确定 IsJITTrackingEnabled 和 IsJITOptimizerDisabled 的设置,以确定程序集是否针对发布进行了优化。

我当前的代码执行 Assembly.LoadFrom 以传入程序集的整个路径并加载它。然后在程序集上执行 GetCustomAttributes 以获取可调试属性。问题是每个程序集都被加载到当前的应用程序域中。因此,如果另一个文件夹使用相同的程序集,它只会使用最初加载的引用。我希望能够加载程序集,读取我需要的属性,然后卸载它。我尝试创建一个新的 appdomain 并将程序集加载到其中,然后卸载程序集后记无济于事。

我知道这一定是可能的,但我不知所措。任何帮助将不胜感激。我也很乐意提供您可能需要的任何其他信息。

4

4 回答 4

20

简短的回答是,不,没有办法按照你的要求去做。

更长的答案是:有一种特殊的程序集加载方法,Assembly.ReflectionOnlyLoad()它使用“仅反射”加载上下文。这使您可以加载无法执行但可以读取其元数据的程序集。

在您的情况下(显然,在我自己想出的每个用例中)它并没有那么有帮助。您不能从这种程序集中获取类型化属性,只有CustomAttributeData. 该类没有提供任何过滤特定属性的好方法(我能想到的最好的方法是将其转换为字符串并使用StartsWith("[System.Diagnostics.Debuggable");

更糟糕的是,仅反射加载不会加载任何依赖程序集,但它会迫使您手动进行。这在客观上比你现在所做的更糟糕;至少现在您可以自动加载依赖项。

(另外,我之前的回答提到了 MEF;我错了,MEF 似乎包含大量自定义反射代码来完成这项工作。)

最终,一旦程序集被加载,您就无法卸载它。您需要卸载整个应用程序域如此 MSDN 文章中所述。

更新:

如评论中所述,我能够通过仅反射加载(和正常加载)获得所需的属性信息,但缺少类型化的属性元数据使其非常痛苦。

如果加载到正常的程序集上下文中,您可以很容易地获得所需的信息:

var d = a.GetCustomAttributes(typeof(DebuggableAttribute), false) as DebuggableAttribute;
var tracking = d.IsJITTrackingEnabled;
var optimized = !d.IsJITOptimizerDisabled;

如果加载到仅反射上下文中,您可以做一些工作;您必须弄清楚属性构造函数采用的形式,知道默认值是什么,并结合这些信息得出每个属性的最终值。你得到你需要的信息是这样的:

var d2 = a.GetCustomAttributesData()
         .SingleOrDefault(x => x.ToString()
                                .StartsWith("[System.Diagnostics.DebuggableAttribute"));

从那里,您需要检查ConstructorArguments以查看调用了哪个构造函数:这个有一个参数或这个有两个参数。然后,您可以使用适当参数的值来确定您感兴趣的两个属性会采用什么值:

if (d2.ConstructorArguments.Count == 1)
{
  var mode = d2.ConstructorArguments[0].Value as DebuggableAttribute.DebuggingModes;
  // Parse the modes enumeration and figure out the values.
}
else
{
  var tracking = (bool)d2.ConstructorArguments[0].Value;
  var optimized = !((bool)d2.ConstructorArguments[1].Value);
}

最后,您需要检查是否NamedArguments可能会覆盖那些在构造函数上设置的内容,例如:

var arg = NamedArguments.SingleOrDefault(x => x.MemberInfo.Name.Equals("IsJITOptimizerDisabled"));
var optimized = (arg == null || !((bool)arg.TypedValue.Value));

最后一点,如果您在 .NET 2.0 或更高版本下运行它,并且还没有看到,MSDN 在DebuggingModes文档中指出了这一点:

在 .NET Framework 2.0 版本中,始终会生成 JIT 跟踪信息,该标志与 Default 的效果相同,只是 IsJITTrackingEnabled 属性为 false,在 2.0 版本中没有任何意义。

于 2012-05-03T21:51:42.247 回答
11

你需要使用Assembly.ReflectionOnlyLoad.

以下是一些MSDN 说明,展示了如何使用它:

using System;
using System.IO;
using System.Reflection;

public class ReflectionOnlyLoadTest
{
    public ReflectionOnlyLoadTest(String rootAssembly) {
        m_rootAssembly = rootAssembly;
    }

    public static void Main(String[] args)
    {
        if (args.Length != 1) {
            Console.WriteLine("Usage: Test assemblyPath");
            return;
        }

        try {
            ReflectionOnlyLoadTest rolt = new ReflectionOnlyLoadTest(args[0]);
            rolt.Run();
        }

        catch (Exception e) {
            Console.WriteLine("Exception: {0}!!!", e.Message);
        }
    }

    internal void Run() {
        AppDomain curDomain = AppDomain.CurrentDomain;
        curDomain.ReflectionOnlyPreBindAssemblyResolve += new ResolveEventHandler(MyReflectionOnlyResolveEventHandler);
        Assembly asm = Assembly.ReflectionOnlyLoadFrom(m_rootAssembly);

        // force loading all the dependencies
        Type[] types = asm.GetTypes();

        // show reflection only assemblies in current appdomain
        Console.WriteLine("------------- Inspection Context --------------");
        foreach (Assembly a in curDomain.ReflectionOnlyGetAssemblies())
        {
            Console.WriteLine("Assembly Location: {0}", a.Location);
            Console.WriteLine("Assembly Name: {0}", a.FullName);
            Console.WriteLine();
        }
    }

    private Assembly MyReflectionOnlyResolveEventHandler(object sender, ResolveEventArgs args) {
        AssemblyName name = new AssemblyName(args.Name);
        String asmToCheck = Path.GetDirectoryName(m_rootAssembly) + "\\" + name.Name + ".dll";
        if (File.Exists(asmToCheck)) {
            return Assembly.ReflectionOnlyLoadFrom(asmToCheck);
        }
        return Assembly.ReflectionOnlyLoad(args.Name);
    }

    private String m_rootAssembly;
}
于 2012-05-03T21:58:32.433 回答
5

永远不可能在当前的 AppDomain 中卸载程序集,不幸的是,这正是 .NET 设计的工作方式。即使是 ReflectionOnly 负载也是如此。这样做也有一点麻烦,因为您需要使用GetCustomAttributesData方法而不是普通的 GetCustomAttributes,因为后者需要在属性构造函数中运行代码。这会使生活更加困难。

一个可行的替代方法是使用Cecil,它允许您检查程序集而无需在正常意义上实际加载它。但这是很多额外的工作。

于 2012-05-03T22:02:19.967 回答
1

我相信 Assembly.ReflectionOnlyLoad 是您正在寻找的。

于 2012-05-03T21:52:23.180 回答