2013-05-03 2 views
4

이미 존재하는 개체에 대해 deserialization 생성자를 호출하려고합니다. 표현식 나무로 어떻게 할 수 있습니까?기존 개체의 표현식 트리를 통해 생성자를 어떻게 호출합니까?

내가 시도 : 문제가 해결되지 않도록

// 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 만,하지 ConstructorInfoMethodInfo을 허용 - MethodInfo로 변환하는 방법이 없다면?

업데이트

난 그냥 ConstructorInfo.Invoke를 사용하여 eneded :

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

나는 그것의 성능을 두려워하지만,이 작업을 수행 할 수있는 유일한 방법이 될 것으로 보인다.

업데이트

이 이제 적절한 대답을 가지고있다. 모두에게 감사드립니다.

+0

"표현식 트리로 어떻게 할 수 있습니까?"정상적인 코드로는 표현식 트리로 할 수 없는데 왜 표현식 트리로 할 수 있다고 생각하십니까? – svick

+0

기존 개체에서 생성자를 호출 하시겠습니까? 너 그렇게 할 수있어. 내가 뭔가를 놓치지 않는 한. – sircodesalot

+0

내일 일부 코드가 생성됩니다. –

답변

6

것은, 당신이 돈 ':이 방법은 BCL에서 어떻게 사용되는지 볼 수있는 모든 방법은 공용 언어 런타임 자체 내에서 구현 internal와 생성자를 호출하는 방법으로 표시 실제 호출이 리플렉션을 필요로하지 않는 한 표현식 트리를 통해 생성자가 호출되는지 여부는 실제로 신경 써야합니다. 다음과 같이 생성자 호출로 전달하는 동적 메서드를 만들 수 있습니다.

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으로 재설정되지 않습니다. 귀하의 생성자가 무엇을하는지에 따라, 이것은 문제 일 수도 있고 아닐 수도 있습니다.

+0

이것이 답인 것처럼 보입니다. 고마워요. – sircodesalot

+0

@sircodesalot 모든 클래스에서이 기능이 작동하지 않는다는 경고를 포함하는 것을 잊어 버렸습니다. 여전히 유용하다는 것을 두 번 확인하십시오 :) – hvd

+0

'x'와 'i'가 있습니다. 그들은 동일하게되어 있습니까? 나는 따라하지 않는다. – sircodesalot

1

개체가 이미 생성 된 경우에는 생성자를 호출 할 수 없습니다. 내가 제대로 질문을 읽고 있어요 경우

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); 
+0

'개체가 이미 생성 된 경우 생성자를 호출 할 수 없습니다. ' 이것은 엄밀히 말해서 사실이 아닙니다. – sircodesalot

+0

나는 당신의 질문에 감사드립니다. 라이브로 배우십시오. –

2

생성자에 대한 (공개) 대리인을 만드는 방법을 만들었습니다. 여러 개의 인수와 가변 개수 (ref 및 out과 같은)를 가진 임의의 (정적 또는 인스턴스) 생성자와 함께 사용할 수 있습니다. 대리자가 void를 반환하면 생성 된 형식의 인스턴스가 첫 번째 매개 변수로 필요합니다. 대리자가 생성 될 형식을 반환하면 새 인스턴스가 만들어집니다.

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

호출을합니다

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

는 대리인, 사용을 만들려면 be :

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

경고!이 메서드를 호출 할 때마다 작은 코드 조각이 생성됩니다. 동일한 생성자에 대해 이러한 함수를 많이 호출하면 캐싱에 대해 생각해보십시오.

5

표현 트리를 사용하려면 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을 사용합니다.

관련 문제