使用IL获取结构的属性值

时间:2017-04-30 23:35:57

标签: c# reflection struct reflection.emit

我正在尝试从以下struct中检索值。

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

它有效,它给了我“Key”而不是“Value”的价值。如果我尝试获取“Key”的值,那么它会给我一个“无法从内存中读取”错误。

以下是上述代码中使用的Get委托的创建。这似乎只是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>;
    }
}

1 个答案:

答案 0 :(得分:1)

您为GetDelegate生成的IL代码中的问题。你应该在调用它的属性GetMethod之前从对象类型参数中取消框结构,如下所示:

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

关于GetGetDelegate方法的小旁注:

  • 对于struct类型,您可以使用Call代替Callvirt,因为它更快,而且结构不具备虚拟属性。
  • 我无法理解你为什么需要用这条指令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>;
}