我正在尝试从以下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>;
}
}
答案 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>;
}