Получение пользовательских атрибутов сборки без загрузки в текущую AppDomain

Я создал небольшое приложение для рекурсивной загрузки сборок в предоставленной директории и чтения их коллекции пользовательских атрибутов. В основном просто для чтения атрибута DebuggableAttribute для определения параметров IsJITTrackingEnabled и IsJITOptimizerDisabled для определения того, оптимизирована ли сборка для выпуска.

Мой текущий код выполняет Assembly.LoadFrom, чтобы передать весь путь к сборке и загрузить его. Затем GetCustomAttributes на сборке получает атрибут отладки. Проблема в том, что каждая сборка загружается в текущую область приложения. Итак, если другая папка использует одну и ту же сборку, она просто использует первоначально загруженную ссылку. Я хотел бы иметь возможность загружать сборку, читать нужные мне свойства, а затем выгружать ее. Я пытаюсь создать новый appdomain и загружать сборки в него, а затем выгружать сборку послесловия безрезультатно.

Я знаю, что это должно быть возможно, но я в недоумении. Любая помощь будет принята с благодарностью. Я был бы рад предоставить любую другую информацию, которая вам может понадобиться.

Ответ 1

Короткий ответ: нет, нет способа сделать то, что вы просите.

Более длинный ответ таков: существует специальный метод загрузки сборок, Assembly.ReflectionOnlyLoad(), который использует контекст нагрузки только для отражения, Это позволяет загружать сборки, которые не могут быть выполнены, но может считывать их метаданные.

В вашем случае (и, по-видимому, в каждом случае, я мог бы придумать), это не очень полезно. Вы не можете получить типизированные атрибуты из этого типа сборки, только CustomAttributeData. Этот класс не обеспечивает хороший способ фильтрации для определенного атрибута (лучшее, что я мог придумать, это передать его в строку и использовать StartsWith("[System.Diagnostics.Debuggable");

Хуже того, только нагрузка с отражением не загружает сборки зависимостей, но это заставляет вас делать это вручную. Это делает его объективно хуже, чем то, что вы делаете сейчас; по крайней мере теперь вы автоматически загружаете загрузку зависимостей.

(Кроме того, в моем предыдущем ответе была сделана ссылка на MEF, я ошибался, похоже, что MEF включает целую тонну кода пользовательского отражения, чтобы сделать эту работу.)

В конечном счете вы не можете выгрузить сборку после ее загрузки. Вам необходимо выгрузить весь домен приложения, как описано в этой статье MSDN.

UPDATE:

Как отмечалось в комментариях, я смог получить необходимую информацию об атрибутах через нагрузку только на отражение (и нормальную нагрузку), но отсутствие типизированных метаданных атрибутов создает серьезную боль.

Если вы загружаетесь в обычный контекст сборки, вы можете легко получить необходимую информацию:

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 является ложным, что не имеет смысла в версии 2.0.

Ответ 2

Вам нужно использовать 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;
}

Ответ 3

Невозможно когда-либо выгрузить сборку в текущем AppDomain, так как .NET предназначен для работы, к сожалению. Это даже в случае с ReflectionOnly нагрузками. Существует также немного морщин с этим, а затем вам нужно использовать метод GetCustomAttributesData вместо обычных GetCustomAttributes, поскольку последний нужен для запуска кода в конструкторе атрибутов. Это может усложнить жизнь.

Альтернативой, которая должна работать, является использование Cecil, который позволяет вам проверять сборку, не загружая ее в обычном смысле. Но это большая работа.

Ответ 4

Я считаю, что Assembly.ReflectionOnlyLoad - это то, что вы ищете.