.NET: Zugreifen auf nicht öffentliche Mitglieder von einer dynamischen Assembly

.net c# dynamic-assemblies expression-trees performance

Frage

Ich arbeite an einer Bibliothek, die es Benutzern erlaubt, beliebige Ausdrücke einzugeben. Meine Bibliothek kompiliert dann diese Ausdrücke als Teil eines größeren Ausdrucks in einen Delegaten. Aus noch unbekannten Gründen führt das Kompilieren des Ausdrucks mit Compile manchmal / oft dazu, dass der Code viel langsamer ist, als wenn er kein kompilierter Ausdruck wäre. Ich habe zuvor eine Frage dazu gestellt, und eine Problemumgehung bestand darin, Compile nicht zu verwenden, sondern CompileToMethod und eine static Methode für einen neuen Typ in einer neuen dynamischen Assembly zu erstellen. Das funktioniert und der Code ist schnell.

Benutzer können jedoch beliebige Ausdrücke System.MethodAccessException , und wenn der Benutzer eine nicht öffentliche Funktion aufruft oder auf ein nicht öffentliches Feld im Ausdruck zugreift, System.MethodAccessException er eine System.MethodAccessException (im Fall einer nichtöffentlichen Methode), wenn Delegat wird aufgerufen.

Was ich hier wahrscheinlich tun könnte, ist einen neuen ExpressionVisitor erstellen, der prüft, ob der Ausdruck auf etwas nicht-öffentliches zugreift und in diesen Fällen den langsameren Compile verwendet, aber ich möchte lieber, dass die dynamische Assembly irgendwie die Rechte erhält, auf das nicht-öffentliche zuzugreifen Mitglieder. Oder finde heraus, ob es etwas gibt, was ich tun kann, wenn Compile langsamer ist (manchmal).

Der vollständige Code, um dieses Problem zu reproduzieren:

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

Akzeptierte Antwort

Das Problem besteht nicht in den Berechtigungen, da keine Berechtigung vorhanden ist, die Ihnen den Zugriff auf ein nicht öffentliches Feld oder Mitglied einer anderen Klasse ohne Reflektion ermöglichen. Dies entspricht der Situation, in der Sie zwei nicht dynamische Assemblys kompiliert haben und eine Assembly eine öffentliche Methode in der zweiten Assembly aufruft. Wenn Sie die Methode dann in "private" ändern, ohne die erste Assembly erneut zu kompilieren, wird der Aufruf der ersten Assemblys zur Laufzeit fehlschlagen . Mit anderen Worten, der Ausdruck in Ihrer dynamischen Assembly wird in einen gewöhnlichen Methodenaufruf kompiliert, den er nicht mehr als in einer anderen Klasse aufrufen kann.

Da keine Berechtigung Ihr Problem lösen kann, können Sie nicht öffentliche Feld- und Methodenreferenzen in Teilausdrücke transformieren, die Reflektion verwenden.

Hier ist ein Beispiel aus Ihrem Testfall. Dies schlägt fehl:

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

aber das wird gelingen:

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

Da dies nicht mit einer Ausnahme abstürzt, können Sie sehen, dass Ihre dynamische Assembly über die Berechtigung reflection verfügt und auf die private Methode zugreifen kann. Sie kann dies jedoch nicht mit einem gewöhnlichen Methodenaufruf tun, zu dem CompileToMethod führt.


Beliebte Antwort

Wenn die nicht dynamische Assembly von Ihnen erstellt wird, können Sie tatsächlich ein InternalsVisibleTo für die dynamische Assembly einschließen (funktioniert sogar mit einem starken Namen). Das würde es erlauben, interne Mitglieder zu verwenden, was in deinem Fall ausreichen könnte?

Um eine Idee zu bekommen, hier ist ein Beispiel, das zeigt, wie die dynamische Assemblierung von Moq verwendet werden kann, um internes Zeug von einer anderen Assembly zu verwenden: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with- moq /

Wenn dieser Ansatz nicht ausreicht, würde ich eine Kombination aus den Vorschlägen von Rick und Miguel verwenden: Erstellen Sie für jeden Aufruf eines nicht-öffentlichen Members "proxy" DynamicMethods und ändern Sie den Ausdrucksbaum so, dass sie anstelle der ursprünglichen Aufrufe verwendet werden.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow