Comment appeler un constructeur via un arbre d'expression sur un objet existant?

c# expression-trees serialization

Question

J'essaie d'appeler le constructeur de désérialisation pour un objet qui existe déjà. Comment puis-je faire cela avec les arbres d'expression?

J'ai essayé:

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

mais Expression.Call accepte uniquement MethodInfo pas ConstructorInfo , donc cela ne fonctionne pas - sauf s’il existe un moyen de convertir en MethodInfo ?

Mettre à jour

J'ai juste utilisé 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() });

J'ai peur de la performance là-dessus, mais cela semble être le seul moyen de le faire.

Mettre à jour

Cela a une réponse appropriée maintenant. Merci a tous.

Réponse acceptée

Si je lis bien votre question, le fait que le constructeur soit appelé via un arbre d'expression ne vous importe pas vraiment, tant que l'invocation ne nécessite pas de réflexion. Vous pouvez construire une méthode dynamique qui transfère vers un appel de constructeur:

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

Notez cependant que cela se comporte comme un appel de méthode ordinaire. x n'est pas défini avant l'impression de sa valeur. Par conséquent, il n'est pas réinitialisé à 0 lorsque vous appelez à nouveau le constructeur. Selon ce que fait votre constructeur, cela peut poser problème ou non.


Réponse populaire

Si vous souhaitez utiliser des arbres d'expression, utilisez Expression.New . Voici un exemple

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

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

Cela ne fonctionne pas sur un objet existant, mais puisque votre code indique GetUninitializedObject je pense que vous pouvez simplement supprimer cette partie et utiliser Expression.New pour créer un nouvel objet.



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