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

c# expression-trees serialization

Question

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.

Accepted Answer

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.


Popular Answer

If you want to use expression trees, use Expression.New. Here's an example

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

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

This doesn't work on an existing object, but since your code shows GetUninitializedObject I would think you could just remove that part and use Expression.New to create a new object.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why