¿Cómo se llama a un constructor a través de un árbol de expresión en un objeto existente?

c# expression-trees serialization

Pregunta

Estoy intentando llamar al constructor de deserialización para un objeto que ya existe. ¿Cómo hago eso con los árboles de expresión?

Lo intenté:

// Create an uninitialized object
T graph = (T)FormatterServices.GetUninitializedObject(graphType);

// (graph, serializationInfo, streamingContext) => graph.Constructor(serializationInfo, streamingContext)
ParameterExpression graphParameter = Expression.Parameter(serializationPack.SelfSerializingBaseClassType, "graph");
ParameterExpression serializationInfoParameter = Expression.Parameter(typeof(SerializationInfo), "serializationInfo");
ParameterExpression streamingContextParameter = Expression.Parameter(typeof(StreamingContext), "streamingContext");

MethodCallExpression callDeserializationConstructor = Expression.Call(graphParameter,
    (MethodInfo)serializationPack.SelfSerializingBaseClassType.GetConstructor(new[] { typeof(SerializationInfo), typeof(StreamingContext) }), 
        new[] { serializationInfoParameter, streamingContextParameter });

pero Expression.Call solo acepta MethodInfo no ConstructorInfo , por lo que no funciona, ¿a menos que haya una forma de convertir a MethodInfo ?

Actualizar

Me puse en marcha simplemente usando ConstructorInfo.Invoke :

// Cache this part
ConstructorInfo deserializationConstructor = serializationPack
    .SelfSerializingBaseClassType
    .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, CallingConventions.Standard,
        new[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);

// Call this when I need it
deserializationConstructor.Invoke(graph, new Object[] { serializationInfo, new StreamingContext() });

Me da miedo el rendimiento, pero parece ser la única forma de hacerlo.

Actualizar

Esto tiene una respuesta adecuada ahora. Gracias a todos.

Respuesta aceptada

Si estoy leyendo su pregunta correctamente, realmente no le importa si se llama al constructor a través de un árbol de expresiones, siempre y cuando la invocación real no requiera reflexión. Puede crear un método dinámico que reenvíe a una llamada de constructor:

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApplication1
{
    static class Program
    {
        static void Main(string[] args)
        {
            var constructor = typeof(Foo).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            var helperMethod = new DynamicMethod(string.Empty, typeof(void), new[] { typeof(Foo) }, typeof(Foo).Module, true);
            var ilGenerator = helperMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Call, constructor);
            ilGenerator.Emit(OpCodes.Ret);
            var constructorInvoker = (Action<Foo>)helperMethod.CreateDelegate(typeof(Action<Foo>));

            var foo = Foo.Create();
            constructorInvoker(foo);
            constructorInvoker(foo);
        }
    }

    class Foo
    {
        int x;

        public static Foo Create()
        {
            return new Foo();
        }

        private Foo()
        {
            Console.WriteLine("Constructor Foo() called, GetHashCode() returns {0}, x is {1}", GetHashCode(), x);
            x++;
        }
    }   
}

Tenga en cuenta que esto se comporta como una llamada de método regular. x no se establece antes de imprimir su valor, por lo que no se restablece a 0 cuando llama al constructor nuevamente. Dependiendo de lo que haga su constructor, esto puede o no ser un problema.


Respuesta popular

Si desea utilizar árboles de expresiones, use Expression.New . Aquí un ejemplo

var info = Expression.Parameter(typeof(SerializationInfo), "info");
var context = Expression.Parameter(typeof(StreamingContext), "context");

var callTheCtor = Expression.New(ctorInfo, info, context);

Esto no funciona en un objeto existente, pero como su código muestra GetUninitializedObject , creo que podría eliminar esa parte y usar Expression.New para crear un nuevo objeto.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow