2017-04-30 3 views
2

J'essaie de récupérer une valeur à partir de struct.Obtention d'une valeur de propriété en utilisant IL pour une structure

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

en utilisant le code suivant.

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 

Et cela fonctionne, mais il me donne la valeur de « clé » et non « Valeur ». Si j'essaie d'obtenir la valeur de "Key" alors il me donne une erreur "Ne peut pas lire de la mémoire".

Voici la création du délégué Get utilisé dans le code ci-dessus. Cela ne semble être un problème qu'avec un 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

Pourriez-vous s'il vous plaît fournir un [mcve]? Il y a quelques bits externes que vous utilisez ici qui ne sont pas définis. – DavidG

+0

@DavidG Classe complète ajoutée – Schotime

Répondre

1

Problème dans le code IL que vous générez pour GetDelegate. Vous devez unbox struct du paramètre de type d'objet avant d'appeler GetMethod de sa propriété, comme ceci:

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

côté des petites notes au sujet GetGetDelegate méthode:

  • Pour struct types vous pouvez utiliser Call à la place Callvirt parce qu'il est plus rapide et struct ne peut pas avoir de propriétés virtuelles.
  • Je ne pouvais pas comprendre pourquoi vous avez besoin de déclarer la variable locale avec cette instruction getIL.DeclareLocal(typeof(object)) et je crois que vous pouvez supprimer cette ligne en toute sécurité.

Juste au cas ici la version de la méthode GetGetDelegate qui a fonctionné pour moi:

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