我终于理解了TypedReference.MakeTypedReference
方法的用法,但为什么这些论点如此有限?底层私有InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType)
可以比MakeTypedReference
做更多的事情,它限制字段数组使元素和字段类型非原始。
我制作了一个示例用法代码,显示了它的全部可能性:
private static readonly MethodInfo InternalMakeTypedReferenceMethod = typeof(TypedReference).GetMethod("InternalMakeTypedReference", flags);
private static readonly Type InternalMakeTypedReferenceDelegateType = ReflectionTools.NewCustomDelegateType(InternalMakeTypedReferenceMethod.ReturnType, InternalMakeTypedReferenceMethod.GetParameters().Select(p => p.ParameterType).ToArray());
private static readonly Delegate InternalMakeTypedReference = Delegate.CreateDelegate(InternalMakeTypedReferenceDelegateType, InternalMakeTypedReferenceMethod);
public static void MakeTypedReference([Out]TypedReference* result, object target, params FieldInfo[] fields)
{
IntPtr ptr = (IntPtr)result;
IntPtr[] flds = new IntPtr[fields.Length];
Type lastType = target.GetType();
for(int i = 0; i < fields.Length; i++)
{
var field = fields[i];
if(field.IsStatic)
{
throw new ArgumentException("Field cannot be static.", "fields");
}
flds[i] = field.FieldHandle.Value;
lastType = field.FieldType;
}
InternalMakeTypedReference.DynamicInvoke(ptr, target, flds, lastType);
}
不幸的是,实际调用它需要更多的黑客攻击,因为无法从MethodInfo
调用并且一个参数是RuntimeType
,因此必须动态生成委托类型(DynamicMethod
也可以使用)。
现在这可以做什么?它可以访问任何对象的任何值的任何字段(类或结构类型,甚至原始),没有任何限制。此外,它可以创建对盒装值类型的引用。
object a = 98;
TypedReference tr;
InteropTools.MakeTypedReference(&tr, a);
Console.WriteLine(__refvalue(tr, int)); //98
__refvalue(tr, int) = 1;
Console.WriteLine(a); //1
那么为什么开发人员似乎毫无意义地决定不允许这种用法,而这显然有用呢?
答案 0 :(得分:1)
责备Plato ......
这是任何ref
(托管指针)引用的固有特性 - 包括新的C#7 ref local and ref return功能 - 正如您所见,TypedReference,您可以将读取和写入用于目标。因为这不是重点吗?
现在因为CTS无法排除其中任何一种可能性,强类型要求每个Type
的{{1}}都要受到以上}的限制在类型层次结构中,em>和在下面。
更正式地说,ref
被限制为多态covariance and contravariance的 交集 ,否则它将符合条件。显然,这个交叉点的结果会折叠成一个Type
本身,它本身就是不变的。
答案 1 :(得分:0)
那么为什么开发人员似乎无意识地决定禁止这种用法,而这显然是有用的?
因为如果我们有字段,我们就不需要它。
您在这里以非常复杂的方式执行的操作基本上是:
((Int32)a).m_value = 1;
当然,在纯C#中我们无法执行此操作,因为CS0445类似((Point)p).X = 1
的分配失败:无法修改拆箱转换的结果。
更不用说Int32.m_value
是int了,它也是Int32
结构。您无法在C#中创建这样的值类型:CS0523:结构成员导致结构布局中的循环。
MakeTypedReference
实际上返回TypedReference
的{{1}}。不受限制的变体的更简洁的版本可能是:
FieldInfo
因此,如果// If target contains desiredField, then returns it as a TypedReference,
// otherwise, returns the reference to the last field
private static unsafe void MakeTypedReference(TypedReference* result, object target, FieldInfo desiredField = null)
{
var flds = new List<IntPtr>();
Type lastType = target.GetType();
foreach (FieldInfo f in target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
flds.Add(f.FieldHandle.Value);
lastType = f.FieldType;
if (f == desiredField)
break;
}
InternalMakeTypedReference.DynamicInvoke((IntPtr)result, target, flds.ToArray(), lastType);
}
是target
,它将返回对int
字段的引用,该字段是int值本身。
但是如果您仍然要处理m_value
,则使用其FieldInfo
可以达到相同的效果要简单得多:
SetValue
如果您确实要使用object a = 98;
FieldInfo int32mValue = typeof(int).GetTypeInfo().GetDeclaredField("m_value");
int32mValue.SetValue(a, 1);
Console.WriteLine(a); // 1
(不使用反射API),则可以直接在对象上使用它,然后通过此引用访问装箱的值。您只需要了解托管对象引用的内存布局即可:
TypedReference
但是实际上这只是比object a = 98;
// pinning is required to prevent GC reallocating the object during the pointer operations
var objectPinned = GCHandle.Alloc(a, GCHandleType.Pinned);
try
{
TypedReference objRef = __makeref(a);
// objRef.Value->object->boxed content
int* rawContent = (int*)*(IntPtr*)*(IntPtr*)&objRef;
// A managed object reference points to the type handle
// (that is another pointer to the method table), which is
// followed by the first field.
if (IntPtr.Size == 4)
rawContent[1] = 1;
else
rawContent[2] = 1;
}
finally
{
objectPinned.Free();
}
Console.WriteLine(a); // 1
版本快一点,主要是由于对象固定。