问题
I've finally understood the usage of the TypedReference.MakeTypedReference method, but why are the arguments so limited? The underlying private InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType)
can do a lot more things than the MakeTypedReference
that limits the field array to have elements and the field types to be non-primitive.
I've made a sample usage code that shows the full possibility of it:
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);
}
Unfortunately, actually calling it needs more hacks, as it can't be invoked from MethodInfo
and one parameter is RuntimeType
, so the delegate type has to be generated dynamically (DynamicMethod
can be also used).
Now what can this do? It can access any field (class or struct type, even primitive) of any value of any object without limitations. Moreover, it can create a reference to a boxed value type.
object a = 98;
TypedReference tr;
InteropTools.MakeTypedReference(&tr, a);
Console.WriteLine(__refvalue(tr, int)); //98
__refvalue(tr, int) = 1;
Console.WriteLine(a); //1
So why have the developers seemingly senselessly decided to disallow this kind of usage, while this is obviously useful?
回答1:
Blame Plato and his darn "theory of types"...
It is the inherent in the nature of any ref
(managed pointer) reference--including the new C# 7 ref local and ref return features--and as you observe, TypedReference, that you can use such for both reading and writing to the target. Because isn't that the whole point?
Now because the CTS can't rule-out either of those possibilities, strong-typing requires that the Type
of every ref
be constrained from both above and below in the type hierarchy.
More formally, the Type
is constrained to be the intersection of the polymorphic covariance and contravariance for which it would otherwise be eligible. Obviously, the result of this intersection collapses to a single Type
, itself, which is henceforth invariant.
回答2:
So why have the developers seemingly senselessly decided to disallow this kind of usage, while this is obviously useful?
Because we don't need it if we have fields.
What you are doing here in a very complicated way is basically the following:
((Int32)a).m_value = 1;
Of course, in pure C# we cannot do this because a ((Point)p).X = 1
like assignment fails with CS0445: Cannot modify the result of an unboxing conversion.
Not to mention that Int32.m_value
is int, which is the Int32
struct again. You cannot create such a value type in C#: CS0523: Struct member causes a cycle in the struct layout.
The MakeTypedReference
actually returns a TypedReference
for a FieldInfo
. A little bit cleaner version of the unrestricted variant could be:
// 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);
}
So if target
is an int
, it returns the reference to the m_value
field, which is the int value itself.
But if you deal with a FieldInfo
anyway, it is much more simple to use its SetValue
for the same effect:
object a = 98;
FieldInfo int32mValue = typeof(int).GetTypeInfo().GetDeclaredField("m_value");
int32mValue.SetValue(a, 1);
Console.WriteLine(a); // 1
If you really want to use a TypedReference
(without the reflection API), then you can use it directly on the object and then access the boxed value via this reference. All you need to know is the memory layout of a managed object reference:
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
But actually this is just a bit faster than the FieldInfo.SetValue
version, mainly due to the object pinning.
来源:https://stackoverflow.com/questions/26998758/why-is-typedreference-maketypedreference-so-constrained