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

しかし、 notExpression.Callのみを受け入れるため、それは機能しません - に変換する方法がない限り、?MethodInfoConstructorInfoMethodInfo

アップデート

私はただ使ってしまいました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 に答える