2017-04-30 7 views
2

다음 struct에서 값을 검색하려고합니다.struct에 대해 IL을 사용하여 속성 값 가져 오기

public struct MyType 
{ 
    public string Key { get; set; } 
    public string Value { get; set; } 
} 

다음 코드를 사용하십시오.

var mem = new MemberAccessor(typeof(MyType), "Value"); 
var r = new MyType {Key = "One", Value = "Two"}; 
object s = mem.Get(r); 
Console.WriteLine(s); //Should be Two but is One 

그리고 그것은 작동 하지만 그것은 나에게 "키"없습니다 "값"의 값을 제공합니다. "키"값을 얻으려고하면 "메모리에서 읽을 수 없습니다"라는 오류 메시지가 표시됩니다. 오류.

위 코드에서 사용 된 대리자는 다음과 같습니다. 이 문제는 struct 일 경우에만 발생합니다.

public class MemberAccessor 
{ 
    private readonly Type _targetType; 
    private readonly Type _memberType; 
    private readonly MemberInfo _member; 

    private static readonly Hashtable _mTypeHash = new Hashtable 
    { 
     [typeof(sbyte)] = OpCodes.Ldind_I1, 
     [typeof(byte)] = OpCodes.Ldind_U1, 
     [typeof(char)] = OpCodes.Ldind_U2, 
     [typeof(short)] = OpCodes.Ldind_I2, 
     [typeof(ushort)] = OpCodes.Ldind_U2, 
     [typeof(int)] = OpCodes.Ldind_I4, 
     [typeof(uint)] = OpCodes.Ldind_U4, 
     [typeof(long)] = OpCodes.Ldind_I8, 
     [typeof(ulong)] = OpCodes.Ldind_I8, 
     [typeof(bool)] = OpCodes.Ldind_I1, 
     [typeof(double)] = OpCodes.Ldind_R8, 
     [typeof(float)] = OpCodes.Ldind_R4 
    }; 

    public static Type GetMemberInfoType(MemberInfo member) 
    { 
     Type type; 
     if (member is FieldInfo) 
      type = ((FieldInfo)member).FieldType; 
     else if (member is PropertyInfo) 
      type = ((PropertyInfo)member).PropertyType; 
     else if (member == null) 
      type = typeof(object); 
     else 
      throw new NotSupportedException(); 

     return type; 
    } 

    /// <summary> 
    /// Creates a new property accessor. 
    /// </summary> 
    /// <param name="targetType">Target object type.</param> 
    /// <param name="memberName">Property name.</param> 
    public MemberAccessor(Type targetType, string memberName) 
    { 
     _targetType = targetType; 
     MemberInfo memberInfo = (targetType).GetProperties().First(x => x.Name == memberName); 

     if (memberInfo == null) 
     { 
      throw new Exception(string.Format("Property \"{0}\" does not exist for type " + "{1}.", memberName, targetType)); 
     } 

     var canRead = IsField(memberInfo) || ((PropertyInfo)memberInfo).CanRead; 
     var canWrite = IsField(memberInfo) || ((PropertyInfo)memberInfo).CanWrite; 

     // roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField; 
     if (!canWrite) 
     { 
      var backingFieldName = $"<{memberName}>k__BackingField"; 
      var backingFieldMemberInfo = targetType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(x => x.Name == backingFieldName); 
      if (backingFieldMemberInfo != null) 
      { 
       memberInfo = backingFieldMemberInfo; 
       canWrite = true; 
      } 
     } 

     _memberType = GetMemberInfoType(memberInfo); 
     _member = memberInfo; 

     if (canWrite) 
     { 
      SetDelegate = GetSetDelegate(); 
     } 

     if (canRead) 
     { 
      GetDelegate = GetGetDelegate(); 
     } 
    } 

    private Func<object, object> GetDelegate = null; 

    private Action<object, object> SetDelegate = null; 

    /// <summary> 
    /// Sets the property for the specified target. 
    /// </summary> 
    /// <param name="target">Target object.</param> 
    /// <param name="value">Value to set.</param> 
    public void Set(object target, object value) 
    { 
     SetDelegate?.Invoke(target, value); 
    } 

    public object Get(object target) 
    { 
     return GetDelegate?.Invoke(target); 
    } 

    private Action<object, object> GetSetDelegate() 
    { 
     Type[] setParamTypes = new Type[] { typeof(object), typeof(object) }; 
     Type setReturnType = null; 

     var owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType; 
     var setMethod = owner != null 
      ? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true) 
      : new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true); 
     // From the method, get an ILGenerator. This is used to 
     // emit the IL that we want. 
     // 
     ILGenerator setIL = setMethod.GetILGenerator(); 
     // 
     // Emit the IL. 
     // 

     Type paramType = _memberType; 
     setIL.Emit(OpCodes.Ldarg_0); //Load the first argument 
     //(target object) 
     //Cast to the source type 
     setIL.Emit(OpCodes.Castclass, this._targetType); 
     setIL.Emit(OpCodes.Ldarg_1); //Load the second argument 
     //(value object) 
     if (paramType.GetTypeInfo().IsValueType) 
     { 
      setIL.Emit(OpCodes.Unbox, paramType); //Unbox it 
      if (_mTypeHash[paramType] != null) //and load 
      { 
       OpCode load = (OpCode)_mTypeHash[paramType]; 
       setIL.Emit(load); 
      } 
      else 
      { 
       setIL.Emit(OpCodes.Ldobj, paramType); 
      } 
     } 
     else 
     { 
      setIL.Emit(OpCodes.Castclass, paramType); //Cast class 
     } 

     if (IsField(_member)) 
     { 
      setIL.Emit(OpCodes.Stfld, (FieldInfo)_member); 
     } 
     else 
     { 
      MethodInfo targetSetMethod = GetSetMethodOnDeclaringType(((PropertyInfo)this._member)); 
      if (targetSetMethod != null) 
      { 
       setIL.Emit(OpCodes.Callvirt, targetSetMethod); 
      } 
      else 
      { 
       setIL.ThrowException(typeof(MissingMethodException)); 
      } 
     } 
     setIL.Emit(OpCodes.Ret); 

     var del = setMethod.CreateDelegate(Expression.GetActionType(setParamTypes)); 
     return del as Action<object, object>; 
    } 

    public static bool IsField(MemberInfo member) 
    { 
     return member is FieldInfo; 
    } 

    public static MethodInfo GetSetMethodOnDeclaringType(PropertyInfo propertyInfo) 
    { 
     var methodInfo = propertyInfo.GetSetMethod(true); 
     return methodInfo ?? propertyInfo 
        .DeclaringType 
        .GetProperty(propertyInfo.Name) 
        .GetSetMethod(true); 
    } 

    private Func<object, object> GetGetDelegate() 
    { 
     Type[] setParamTypes = new[] { typeof(object) }; 
     Type setReturnType = typeof(object); 

     Type owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType; 
     var getMethod = owner != null 
      ? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true) 
      : new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true); 

     // From the method, get an ILGenerator. This is used to 
     // emit the IL that we want. 
     ILGenerator getIL = getMethod.GetILGenerator(); 

     getIL.DeclareLocal(typeof(object)); 
     getIL.Emit(OpCodes.Ldarg_0); //Load the first argument 
     //(target object) 
     //Cast to the source type 
     getIL.Emit(OpCodes.Castclass, this._targetType); 

     //Get the property value 

     if (IsField(_member)) 
     { 
      getIL.Emit(OpCodes.Ldfld, (FieldInfo)_member); 
      if (_memberType.GetTypeInfo().IsValueType) 
      { 
       getIL.Emit(OpCodes.Box, _memberType); 
      } 
     } 
     else 
     { 
      var targetGetMethod = ((PropertyInfo)_member).GetGetMethod(); 
      getIL.Emit(OpCodes.Callvirt, targetGetMethod); 
      if (targetGetMethod.ReturnType.GetTypeInfo().IsValueType) 
      { 
       getIL.Emit(OpCodes.Box, targetGetMethod.ReturnType); 
      } 
     } 

     getIL.Emit(OpCodes.Ret); 

     var del = getMethod.CreateDelegate(Expression.GetFuncType(setParamTypes.Concat(new[] { setReturnType }).ToArray())); 
     return del as Func<object, object>; 
    } 
} 
+0

당신은 [mcve]을 제공시겠습니까? 여기서 정의되지 않은 몇 가지 외부 비트가 있습니다. – DavidG

+0

@DavidG Full 클래스 추가됨 – Schotime

답변

1

GetDelegate에 대해 생성하는 IL 코드에 문제가 있습니다. 당신은 그것의 GetMethod를 호출하기 전에 객체 유형 매개 변수에서 구조체를 언 박싱한다 속성은 다음과 같이이다 : 더 빨리 때문에

  • struct 유형의 당신은 Call 대신 Callvirt를 사용할 수 있습니다

    if (_targetType.IsValueType) 
        getIL.Emit(OpCodes.Unbox, _targetType); 
    else        
        getIL.Emit(OpCodes.Castclass, this._targetType); 
    

    작은 측면에 대한 GetGetDelegate 방법을 노트 struct는 가상 속성을 가질 수 없습니다.

  • 왜이 명령으로 로컬 변수를 선언해야하는지 이해할 수 없었습니다. getIL.DeclareLocal(typeof(object))이 줄을 안전하게 제거 할 수 있다고 생각합니다.

그냥 경우에 여기에 나를 위해 일한 GetGetDelegate 방법의 버전 :

private Func<object, object> GetGetDelegate() 
{ 
    Type setParamType = typeof(object); 
    Type[] setParamTypes = { setParamType }; 
    Type setReturnType = typeof(object); 

    Type owner = _targetType.GetTypeInfo().IsAbstract || _targetType.GetTypeInfo().IsInterface ? null : _targetType; 
    var getMethod = owner != null 
     ? new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, owner, true) 
     : new DynamicMethod(Guid.NewGuid().ToString(), setReturnType, setParamTypes, true); 

    // From the method, get an ILGenerator. This is used to 
    // emit the IL that we want. 
    ILGenerator getIL = getMethod.GetILGenerator(); 

    getIL.Emit(OpCodes.Ldarg_0); //Load the first argument (target object) 

    if (_targetType.IsValueType) 
     getIL.Emit(OpCodes.Unbox, _targetType); //unbox struct 
    else 
     getIL.Emit(OpCodes.Castclass, this._targetType); //Cast to the source type 

    Type returnType = null; 
    if (IsField(_member)) 
    { 
     getIL.Emit(OpCodes.Ldfld, (FieldInfo)_member); 
     returnType = _memberType; 
    } 
    else 
    { 
     var targetGetMethod = ((PropertyInfo)_member).GetGetMethod(); 
     var opCode = _targetType.IsValueType ? OpCodes.Call : OpCodes.Callvirt; 
     getIL.Emit(opCode, targetGetMethod); 
     returnType = targetGetMethod.ReturnType; 
    } 

    if (returnType.IsValueType) 
    { 
     getIL.Emit(OpCodes.Box, returnType); 
    } 

    getIL.Emit(OpCodes.Ret); 

    var del = getMethod.CreateDelegate(Expression.GetFuncType(setParamType, setReturnType)); 
    return del as Func<object, object>; 
}