Search code examples
c#serializationexpression-trees

How do you call a constructor via an expression tree on an existing object?


I'm trying to call the deserialization constructor for an object that already exists. How do I do that with expression trees?

I tried:

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

but Expression.Call only accepts MethodInfo not ConstructorInfo, so that doesn't work - unless there is a way to convert to a MethodInfo?

Update

I eneded up just using 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() });

I'm scared of the performance on it, but it seems to be the only way to do this.

Update

This has a proper answer now. Thanks all.


Solution

  • If I'm reading your question correctly, you don't really care whether the constructor is called via an expression tree, so long as the actual invocation doesn't require reflection. You can build a dynamic method that forwards to a constructor call:

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

    Note though that this behaves like a regular method call. x is not set before printing its value, so it does not get reset to 0 when you call the constructor again. Depending on what your constructor does, this may or may not be a problem.