.NET: доступ к непубличным членам из динамической сборки

Я работаю над библиотекой, которая позволяет пользователям вводить произвольные выражения. Затем моя библиотека компилирует эти выражения как часть большего выражения в делегат. Теперь, по неизвестным причинам, компиляция выражения с Compile иногда/часто приводит к коду, который намного медленнее, чем если бы это было не скомпилированное выражение. я задал вопрос об этом до и один обходной путь: не использовать Compile, а CompileToMethod и создать метод static для нового типа в новом динамическая сборка. Это работает, и код работает быстро.

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

Что бы я мог сделать здесь, это создать новый ExpressionVisitor, который проверяет, получает ли выражение доступ к чему-то непубличному и использует медленный Compile в этих случаях, но я предпочел бы, чтобы динамическая сборка каким-то образом права на доступ к непубличным членам. Или выясните, есть ли что-нибудь, что я могу сделать с Compile медленнее (иногда).

Полный код для воспроизведения этой проблемы:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DynamicAssembly
{
  public class Program
  {
    private static int GetValue()
    {
      return 1;
    }

    public static int GetValuePublic()
    {
      return 1;
    }

    public static int Foo;

    static void Main(string[] args)
    {
      Expression<Func<int>> expression = () => 10 + GetValue();

      Foo = expression.Compile()();

      Console.WriteLine("This works, value: " + Foo);

      Expression<Func<int>> expressionPublic = () => 10 + GetValuePublic();

      var compiledDynamicAssemblyPublic = (Func<int>)CompileExpression(expressionPublic);

      Foo = compiledDynamicAssemblyPublic();

      Console.WriteLine("This works too, value: " + Foo);

      var compiledDynamicAssemblyNonPublic = (Func<int>)CompileExpression(expression);

      Console.WriteLine("This crashes");

      Foo = compiledDynamicAssemblyNonPublic();
    }

    static Delegate CompileExpression(LambdaExpression expression)
    {
      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("MyAssembly"+ Guid.NewGuid().ToString("N")), 
        AssemblyBuilderAccess.Run);

      var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

      var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);

      var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
        MethodAttributes.Public | MethodAttributes.Static);

      expression.CompileToMethod(methodBuilder);

      var resultingType = typeBuilder.CreateType();

      var function = Delegate.CreateDelegate(expression.Type, 
        resultingType.GetMethod("MyMethod"));

      return function;
    }
  }
}

Ответы

Ответ 1

Проблема заключается не в разрешениях, потому что нет разрешения, которое может позволить вам получить доступ к непубличному полю или члену другого класса без отражения. Это аналогично ситуации, когда вы скомпилировали две нединамические сборки, а одна сборка вызывает открытый метод во второй сборке. Затем, если вы измените метод на закрытый, не перекомпилируя первую сборку, первый вызов сборок теперь будет завершен во время выполнения. Другими словами, выражение в вашей динамической сборке компилируется в обычный вызов метода, который у него нет разрешения на вызов больше, чем вы делаете из другого класса даже в той же сборке.

Поскольку разрешение не может решить вашу проблему, возможно, вы сможете преобразовать ссылки на непубличные поля и методы в подвыражения, которые используют отражение.

Вот пример, взятый из вашего тестового примера. Это не удается:

Expression<Func<int>> expression = () => 10 + GetValue();

но это будет успешным:

Expression<Func<int>> expression = () => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);

Так как это не сбой с исключением, вы можете видеть, что ваша динамическая сборка имеет разрешение отражения и может обращаться к приватному методу, она просто не может сделать это, используя обычный вызов метода, который приводит к результатам CompileToMethod.

Ответ 2

У меня когда-то была проблема с доступом к закрытым элементам класса, из сгенерированного IL-кода с использованием DynamicMethod.

Оказалось, что произошла перегрузка конструктора класса DynamicMethod, который получает тип класса, в который разрешен доступ частного доступа:

http://msdn.microsoft.com/en-us/library/exczf7b9.aspx

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

Может быть, есть что-то подобное при компиляции деревьев выражений... или что вы можете создать это дерево выражений как DynamicMethod.

Ответ 3

Если нединамическая сборка построена вами, вы можете фактически включить InternalsVisibleTo для динамической сборки (даже работает с сильным именем). Это позволило бы использовать внутренние члены, которых может быть достаточно в вашем случае?

Чтобы получить представление, вот пример, который показывает горячий, чтобы динамическая сборка Moq использовала внутренние вещи из другой сборки: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with-moq/

Если этого подхода недостаточно, я бы предложил комбинацию предложений Рика и Мигеля: создать "прокси" DynamicMethods для каждого вызова в непубличный член и изменить дерево выражений, чтобы они использовались вместо оригинальные вызовы.