.NET: Accéder aux membres non publics à partir d'un assembly dynamique

.net c# dynamic-assemblies expression-trees performance

Question

Je travaille sur une bibliothèque qui permet aux utilisateurs de saisir des expressions arbitraires. Ma bibliothèque compile ensuite ces expressions dans le cadre d'une expression plus grande en un délégué. Or, pour des raisons encore inconnues, la compilation de l'expression avec Compile entraîne parfois un code beaucoup plus lent que ce ne serait le cas s'il ne s'agissait pas d'une expression compilée. J'ai déjà posé une question à ce sujet auparavant et une solution de contournement consistait à ne pas utiliser Compile , mais CompileToMethod et à créer une méthode static sur un nouveau type dans un nouvel assemblage dynamique. Cela fonctionne et le code est rapide.

Mais les utilisateurs peuvent entrer des expressions arbitraires et il s'avère que si l'utilisateur appelle une fonction non publique ou accède à un champ non public de l'expression, il lève une System.MethodAccessException (dans le cas d'une méthode non publique) lorsque le délégué est appelé.

Ce que je pourrais probablement faire ici est de créer un nouveau ExpressionVisitor qui vérifie si l'expression accède à un élément non public et utilise la Compile plus lente dans ces cas, mais je préférerais que l'assembly dynamique obtienne en quelque sorte les droits d'accès au non-public. membres. Ou découvrez si je peux faire quelque chose pour que Compile soit plus lent (parfois).

Le code complet pour reproduire ce problème:

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;
    }
  }
}

Réponse acceptée

Le problème n'est pas les autorisations, car aucune autorisation ne peut vous permettre d'accéder à un champ non public ou à un membre d'une autre classe sans réflexion. Cela est analogue au cas où vous avez compilé deux assemblys non dynamiques et qu'un assemblage appelle une méthode publique dans le deuxième assemblage. Ensuite, si vous définissez la méthode sur privé sans recompiler le premier assemblage, le premier appel des assemblys échouera à l'exécution . En d'autres termes, l'expression de votre assemblage dynamique est en cours de compilation dans un appel de méthode ordinaire qu'il n'a pas la permission d'appeler plus que vous ne le faites depuis une autre classe, même dans le même assemblage.

Comme aucune autorisation ne peut résoudre votre problème, vous pourrez peut-être transformer des références de champs et de méthodes non publics en sous-expressions utilisant la réflexion.

Voici un exemple tiré de votre cas de test. Cela échoue:

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

mais cela va réussir:

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

Comme cela ne plante pas avec une exception, vous pouvez voir que votre assembly dynamique dispose d'une autorisation de réflexion et qu'il peut accéder à la méthode privée. Il ne peut pas le faire à l'aide d'un appel de méthode ordinaire résultant de CompileToMethod


Réponse populaire

Si l'assemblage non dynamique est créé par vous, vous pouvez en fait inclure InternalsVisibleTo pour l'assemblage dynamique (même avec un nom fort). Cela permettrait d'utiliser des membres internes, ce qui peut suffire dans votre cas?

Pour vous faire une idée, voici un exemple qui montre comment permettre à l’assemblage dynamique de Moq d’utiliser des éléments internes d’un autre assemblage: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with- moq /

Si cette approche ne suffit pas, je propose une combinaison des suggestions de Rick et de Miguel: créez des méthodes dynamiques "proxy" pour chaque invocation à un membre non public et modifiez l'arborescence des expressions afin qu'elles soient utilisées à la place des invocations d'origine.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow