2011-04-22 12 views
17

Estoy trabajando en una biblioteca que permite a los usuarios ingresar expresiones arbitrarias. Mi biblioteca luego compila esas expresiones como parte de una expresión más grande en un delegado. Ahora, por razones todavía desconocidas, compilar la expresión con Compile a veces/a menudo da como resultado un código que es mucho más lento de lo que sería si no fuera una expresión compilada. I asked a question about this antes y una solución fue no usar Compile, pero CompileToMethod y crear un método static en un nuevo tipo en un nuevo conjunto dinámico. Eso funciona y el código es rápido..NET: Acceder a miembros no públicos desde un ensamblaje dinámico

Pero los usuarios pueden ingresar expresiones arbitrarias y resulta que si el usuario llama a una función no pública o accede a un campo no público en la expresión, arroja un System.MethodAccessException (en el caso de un método no público) cuando el delegado es invocado

Lo que probablemente podría hacer aquí es crear un nuevo ExpressionVisitor que compruebe si la expresión tiene acceso a algo no público y usar el Compile más lento en esos casos, pero prefiero que el ensamblaje dinámico de alguna manera tenga los derechos de acceso los miembros no públicos. O descubro si hay algo que pueda hacer para que Compile sea más lento (algunas veces).

El código completo para reproducir este problema:

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

No tengo una respuesta para usted, pero ¿por qué es necesario apoyar métodos privados de llamadas? – jlew

+0

Porque el usuario espera que sea posible. Porque * son * accesibles cuando crea la expresión, como '() => CallPrivateMethod()', pero fallarán en el tiempo de ejecución. No hay nada para él que indique que no funciona hasta que lo ejecute y se cuelgue y arda. Eso es realmente malo y viola la regla de "menos sorpresa", así que no puedo justificarlo y tendré que conformarme con el código lento. – JulianR

+0

Tiene sentido, si el usuario es un programador de C# (a diferencia de alguien escribiendo expresiones en un formulario, por ejemplo). ¿Ha evaluado el modo versión versus depuración para el delegado compilado? ¿Cómo se comparan? – jlew

Respuesta

5

El problema no son los permisos porque no hay permiso que le permita acceder a un campo no público o miembro de otra clase sin reflexión. Esto es análogo a la situación en la que compiló dos conjuntos no dinámicos y un conjunto llama a un método público en el segundo conjunto. Luego, si cambia el método a privado sin volver a compilar el primer ensamblado, la primera llamada a los ensamblados ahora fallará en el tiempo de ejecución. En otras palabras, la expresión en su ensamblado dinámico se está compilando en una llamada a método ordinario a la que no tiene permiso para llamar más de lo que lo hace desde otra clase, incluso en el mismo ensamblado.

Dado que ningún permiso puede resolver su problema, es posible que pueda transformar referencias de campos y métodos no públicos en subexpresiones que utilizan la reflexión.

Aquí hay un ejemplo tomado de su caso de prueba.Esta falla:

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

pero esto tendrá éxito:

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

Dado que este no se bloquea con una excepción, se puede ver que su ensamblaje dinámico tiene permiso de reflexión y se puede acceder al método privado, simplemente no puede hacerlo usando una llamada a método ordinario que CompileToMethod resultados en.

1

Una vez que había un problema de acceso a los elementos particulares de una clase, de código IL generado utilizando DynamicMethod.

Resultó que había una sobrecarga del constructor de la clase DynamicMethod que recibe el tipo de clase en el acceso privado wich se permitiría:

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

Este enlace contiene muestras de la forma de acceder datos privados ... Sé que esto no tiene nada que ver con los árboles de expresiones, pero podría darte algunas pistas sobre cómo hacerlo.

Puede haber algún tipo de cosa similar al compilar árboles de expresiones ... o que puede crear ese árbol de expresiones como método dinámico.

+0

Gracias. Pero 'CompileToMethod' solo tiene una forma de llamarse: necesita pasar una instancia de' MethodBuilder'. Y solo puede obtener 'MethodBuilder' de' TypeBuilder' y necesita 'ModuleBuilder' para eso y para * que * necesita' AssemblyBuilder'. – JulianR

1

Si el ensamblaje no dinámico es construido por usted, puede incluir un InternalsVisibleTo para el ensamblaje dinámico (incluso funciona con un nombre fuerte)) Eso permitiría usar miembros internos, ¿cuál puede ser suficiente en su caso?

Para tener una idea, he aquí un ejemplo que muestra caliente para permitir que el conjunto dinámico de Moq utilizar material interno de otro montaje: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with-moq/

Si este enfoque no es suficiente, me gustaría ir con una combinación de las sugerencias de Rick y Miguel: cree "proxy" DynamicMethods para cada invocación a un miembro no público y cambie el árbol de expresiones para que se utilicen en lugar de las invocaciones originales.

Cuestiones relacionadas