获取自定义程序集属性而不将其加载到当前AppDomain中

15
我创建了一个小型应用程序来递归加载指定目录中的程序集,并读取它们的自定义属性集合。主要是读取DebuggableAttribute来确定IsJITTrackingEnabled和IsJITOptimizerDisabled的设置,以确定该程序集是否优化为发布。
我当前的代码使用Assembly.LoadFrom将整个路径传递给程序集并加载它。然后对程序集执行GetCustomAttributes以获取debuggable属性。问题在于每个程序集都被加载到当前的应用程序域中。因此,如果另一个文件夹使用相同的程序集,它只会使用最初加载的引用。我想能够加载程序集,读取我需要的属性,然后卸载它。我尝试创建一个新的应用程序域并将程序集加载到其中,然后卸载程序集,但没有成功。
我知道这一定是可能的,但我不知如何操作。非常感谢任何帮助。我也很乐意提供您可能需要的其他信息。
4个回答

20
简短的回答是,不,没有办法做到你所要求的。
更长的答案是:有一种特殊的程序集加载方法Assembly.ReflectionOnlyLoad(),它使用“仅反射”加载上下文。这使您可以加载无法执行但可以读取其元数据的程序集。
在您的情况下(以及显然在我自己能想到的每个用例中),这并不真正有帮助。您无法从这种类型的程序集中获取已键入的属性,只能使用CustomAttributeData。该类没有提供任何好的方式来过滤特定的属性(我能想到的最好的方法是将其强制转换为字符串并使用StartsWith("[System.Diagnostics.Debuggable");)。
更糟糕的是,仅反射加载不会加载任何依赖程序集,但它强制您手动完成。这使它比您现在正在做的事情更糟糕;至少现在您可以自动加载依赖项。
(此外,我的先前答案提到了MEF;我错了,似乎MEF包括大量自定义反射代码使其工作。)
最终,一旦程序集被加载,就无法卸载它。您需要卸载整个应用程序域,如此MSDN文章所述。
更新:
如评论中所述,我可以通过反射-only加载(和正常加载)获取所需的属性信息,但缺乏类型化的属性元数据使其变得非常棘手。
如果加载到正常的程序集上下文中,则可以轻松获取所需的信息:
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跟踪信息,此标志与默认标志具有相同的效果,但IsJITTrackingEnabled属性为false,在2.0版本中没有任何意义。


谢谢。我尝试过了,但是在通过CustomAttributeData对象访问IsJITTrackingEnabled和IsJITOptimizerDisabled时遇到了困难。我这样做错了吗? - RockyMountainHigh
好的,我能够做到你想要的,但我也发现ReflectionOnlyLoad基本上是无用的。它不会自动加载依赖项--你必须自己加载它们。(我还发现MEF实现了一个自定义反射上下文,并且似乎根本没有使用这个功能。) - Michael Edenfield
你介意分享一下你是如何获取我需要的属性的吗?我已经非常接近了,能够通过委托加载程序集。谢谢! - RockyMountainHigh

12

你需要使用 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;
}

5
很不幸,当前的AppDomain中无法卸载程序集,这是.NET设计的限制。即使使用ReflectionOnly加载也是如此。此外,这样做还有一个小问题,您需要使用GetCustomAttributesData方法而不是常规的GetCustomAttributes方法,因为后者需要在属性构造函数中运行代码。这可能会使生活更加困难。
另一种可行的替代方法是使用Cecil,它允许您检查程序集,而不实际以正常方式加载它。但这需要额外的工作量。

1

我相信 Assembly.ReflectionOnlyLoad 是你要找的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接