6

我正在尝试为已经存在的对象调用反序列化构造函数。我如何使用表达式树来做到这一点?

我试过了:

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

Expression.Call只接受MethodInfonot ConstructorInfo,所以这不起作用 - 除非有办法转换为MethodInfo?

更新

我只使用了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() });

我害怕它的性能,但这似乎是唯一的方法。

更新

现在有了正确的答案。谢谢大家。

4

4 回答 4

10

如果要使用表达式树,请使用Expression.New。这是一个例子

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

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

这不适用于现有对象,但由于您的代码显示GetUninitializedObject,我认为您可以删除该部分并用于Expression.New创建新对象。

于 2013-05-04T14:52:30.420 回答
7

如果我正确阅读了您的问题,那么您并不真正关心是否通过表达式树调用构造函数,只要实际调用不需要反射即可。您可以构建一个转发给构造函数调用的动态方法:

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

请注意,这与常规方法调用类似。在打印它的值之前没有设置,所以当你再次调用构造函数时x它不会被重置。0根据您的构造函数所做的事情,这可能是也可能不是问题。

于 2013-05-03T19:35:21.890 回答
2

我创建了一个方法来为构造函数创建一个(打开的)委托。您可以将它与具有任意数量的参数和变量(如 ref 和 out)的任何(静态或实例)构造函数一起使用。如果您的委托返回 void,则构造类型的实例应作为第一个参数。如果委托返回要构造的类型,则会创建一个新实例。

static public T CreateDelegate<T>(this ConstructorInfo constructor)
{
    // Validate if the constructor is not null.
    if (constructor == null)
        throw new ArgumentNullException("constructor");

    // Validate if T is a delegate.
    Type delegateType = typeof(T);
    if (!typeof(Delegate).IsAssignableFrom(delegateType))
        throw new ArgumentException("Generic argument T must be a delegate.");

    // Get alle needed information.
    MethodInfo invoke = delegateType.GetMethod("Invoke");
    ParameterInfo[] constructorParams = constructor.GetParameters();
    ParameterInfo[] delegateParams = invoke.GetParameters();

    // What kind of delegate is going to be created (open, creational, static).
    bool isOpen = false;
    OpCode opCode = OpCodes.Newobj;
    int parameterOffset = 0;
    if (constructor.IsStatic) // Open delegate.
    {
        opCode = OpCodes.Call;
        if (invoke.ReturnType != typeof(void))
            throw new ArgumentException("Delegate to static constructor cannot have a return type.");
        if (delegateParams.Length != 0)
            throw new ArgumentException("Delegate to static constructor cannot have any parameters.");
    }
    else if (invoke.ReturnType == typeof(void)) // Open delegate.
    {
        opCode = OpCodes.Call;
        isOpen = true;
        parameterOffset = 1;
        if ((delegateParams.Length == 0) || (delegateParams[0].ParameterType != constructor.DeclaringType))
            throw new ArgumentException("An open delegate must have a first argument of the same type as the type that is being constructed.");
    }
    else // Creational delegate.
    {
        if (invoke.ReturnType != constructor.DeclaringType)
            throw new ArgumentException("Return type of delegate must be equal to the type that is being constructed.");
    }

    // Validate the parameters (if any).
    if (constructorParams.Length + parameterOffset != delegateParams.Length)
        throw new ArgumentException(isOpen
            ? "The number of parameters of the delegate (the argument for the instance excluded) must be the same as the number of parameters of the constructor."
            : "The number of parameters of the delegate must be the same as the number of parameters of the constructor.");
    for (int i = 0; i < constructorParams.Length; i++)
    {
        ParameterInfo constructorParam = constructorParams[i];
        ParameterInfo delegateParam = delegateParams[i + parameterOffset];
        if (constructorParam.ParameterType != delegateParam.ParameterType)
            throw new ArgumentException("Arguments of constructor and delegate do not match.");
    }

    // Create the dynamic method.
    DynamicMethod method = new DynamicMethod(
            "",
            invoke.ReturnType,
            delegateParams.Select(p => p.ParameterType).ToArray(),
            constructor.DeclaringType.Module,
            true);


    // Create the IL.
    ILGenerator gen = method.GetILGenerator();
    for (int i = 0; i < delegateParams.Length; i++)
        gen.Emit(OpCodes.Ldarg, i);
    gen.Emit(opCode, constructor);
    gen.Emit(OpCodes.Ret);

    // Return the delegate :)
    return (T)(object)method.CreateDelegate(delegateType);
}

要创建委托,请使用:

public class MyObject
{
    public MyObject(int anyValue)
    {
        ...
    }
}

Action<MyObject, int> c = typeof(MyObject)
    .GetConstructor(new [] { typeof(int) })
    .CreateDelegate<Action<MyObject, int>>();
MyObject myObject = new MyObject(1;
c(myObject, 2);

这一切都可以通过添加一个额外的函数来缩短一点:

static public T CreateConstructorDelegate<T>(this Type type)
{
    // Validate if the constructor is not null.
    if (type == null)
        throw new ArgumentNullException("type");

    // Validate if T is a delegate.
    Type delegateType = typeof(T);
    if (!typeof(Delegate).IsAssignableFrom(delegateType))
        throw new ArgumentException("Generic argument T must be a delegate.");

    // Validate the delegate return type
    MethodInfo invoke = delegateType.GetMethod("Invoke");
    int parameterOffset = 0;
    BindingFlags binding = BindingFlags.Public | BindingFlags.Instance;
    if (invoke.ReturnType == typeof(void))
    {
        if (invoke.GetParameters().Length == 0)
            binding = BindingFlags.NonPublic | BindingFlags.Static; // For static constructors.
        else
            parameterOffset = 1; // For open delegates.
    }
    // Validate the signatures
    ParameterInfo[] delegateParams = invoke.GetParameters();
    ConstructorInfo constructor = type.GetConstructor(binding, null, delegateParams.Skip(parameterOffset).Select(p => p.ParameterType).ToArray(), null);
    if (constructor == null)
        throw new ArgumentException("Constructor with specified parameters cannot be found.");

    return constructor.CreateDelegate<T>();
}

创建委托的调用现在将是:

Action<MyObject, int> c = typeof(MyObject)
    .CreateConstructorDelegate<Action<MyObject, int>>();
// Call constructor.
MyObject myObject = new MyObject(1);
// Call constructor again on same object.
c(myObject, 2);

警告! 每次调用这些方法时,都会创建一小段代码。如果您为同一个构造函数多次调用这些函数,请考虑缓存。

于 2013-05-04T12:33:03.863 回答
1

如果对象已经创建,则不能调用构造函数。你可以看到这个方法在 BCL 中是如何使用的,所有标记为的internal方法和调用构造函数的方法都是在公共语言运行时本身内实现的:

RuntimeConstructorInfo constructor = ObjectManager.GetConstructor(t);
object uninitializedObject = FormatterServices.GetUninitializedObject((Type) this.m_realType);
constructor.SerializationInvoke(uninitializedObject, this.m_savedSerializationInfo, context);

[DebuggerHidden]
[SecuritySafeCritical]
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.InternalCall)]
internal static void SerializationInvoke(IRuntimeMethodInfo method, object target, SerializationInfo info, ref StreamingContext context);
于 2013-05-03T17:03:11.107 回答