【问题标题】:How do you call a constructor via an expression tree on an existing object?如何通过现有对象上的表达式树调用构造函数?
【发布时间】:2015-09-09 17:14:52
【问题描述】:

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

我试过了:

// 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 只接受MethodInfo 而不是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() });

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

更新

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

【问题讨论】:

  • “如何用表达式树做到这一点?”为什么你认为你可以用表达式树来做到这一点,而你不能用普通代码做到这一点?
  • 在现有对象上调用构造函数?你可以这样做。除非我错过了什么。
  • 明天我会写一些代码。
  • 满怀期待。
  • 哇,我不知道你可以使用反射来做到这一点,这是邪恶的。我认为你不应该这样做,除非绝对必要。每个对象的构造函数应该只运行一次(除非您使用构造函数链接)。

标签: c# serialization expression-trees


【解决方案1】:

如果要使用表达式树,请使用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 创建一个新对象。

【讨论】:

    【解决方案2】:

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

    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。根据您的构造函数所做的,这可能是也可能不是问题。

    【讨论】:

    • 这似乎是答案。谢谢。
    • @sircodesalot 我忘了包含一个警告,为什么这不适用于所有课程,请仔细检查以确保它对您仍然有用:)
    • 你有'x'和'i'。他们应该是一样的吗?我不太明白。
    • @sircodesalot 是的,它们应该是一样的,这是一个错字。如果你只构造一次对象,这不会是一个问题,但是如果你在构造函数已经被调用之后再次调用它,结果可能不是你想要的。
    • 啊,我用的是FormatterServices.GetUninitializedObject,它根本不做任何构造。在一般情况下,我只是从我正在序列化的对象中提取所有 fieldinfo 数据,然后在反序列化时将其粘贴回空壳。但是,实现ISerializable 的东西需要我调用“反序列化构造函数”才能执行它自己的反序列化(因此是问题所在)。我假设如果它实现ISerializable,它应该会发生这样的事情(后期构造)。
    【解决方案3】:

    我创建了一个方法来为构造函数创建一个(打开的)委托。您可以将它与具有任意数量的参数和变量(如 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);
    

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

    【讨论】:

      【解决方案4】:

      如果对象已经创建,则不能调用构造函数。你可以看到这个方法在 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);
      

      【讨论】:

      • You cannot call the constructor if the object has already been created。严格来说,这不是真的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多