c# - Obtener el campo al que se refiere el token de metadatos de MemberRef
.net reflection (1)
Advertencia justa, esto puede ser un poco esotérico y complicado.
Dado un MemberRef (más explicación a continuación) extraído de un flujo de CIL, ¿cómo averigua qué campo, si lo hay, señala (y obtiene un FieldInfo
para él)?
Esto es lo que he descubierto hasta ahora
De acuerdo con el estándar ECMA 335 , MemberRef es un token de metadatos que es básicamente una búsqueda en una tabla que podría apuntar a un token de metadatos de campo o un token de metadatos de método. Cualquier token de metadatos que comience con 0x0A es un MemberRef.
No había encontrado uno de estos antes, pero no parecen ser tan infrecuentes. Pude obtener una generada usando el siguiente tipo anónimo en un método:
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
}
Cuando tomo el cuerpo del método a través de la reflexión (obtener el PropertyInfo , obtener el GetMethod , obtener el MethodBody , y finalmente obtener el IL ) el método de obtención de A es:
[2, 123, 79, 0, 0, 10, 42]
Que se convierte en:
ldarg.0
ldfld 0x0A00004F
ret
Si reflexiono y obtengo el campo de respaldo (confiando en la similitud de nombre para elegir <A>i__Field
, nada algorítmico) veo que el MetadataToken es 0x04000056
.
Tenga en cuenta que los tokens generados pueden variar entre las compilaciones.
Un token que comienza 0x04 es un campo:
La mayoría de las veces (para todos los objetos no anónimos en mi prueba limitada, de hecho) la IL contiene un token de metadatos de campo. Esto es fácil de convertir en FieldInfo
través de Module.ResolveField(int)
, ya que averiguar qué hacer con un MemberRef me está haciendo tropezar.
Recorrer los demás métodos de ResolveXXX en el Module , el único que puede hacer algo con un MemberRef es ResolveSignature
. Cuando se ejecuta en el MemberRef anterior, devuelve una matriz de [6, 19, 0]
. Realmente no sé qué hacer con eso.
El código en el que estoy trabajando no está terminado, pero es público. El error se puede ver al ejecutar esta prueba , lo que hace que se genere una excepción cuando una búsqueda de campo falla en esta línea . Tenga en cuenta que la prueba en sí no está terminada, aún no se espera que tenga éxito, pero tampoco debería morir allí.
¿Alguien tiene alguna idea de qué hacer con esa firma, o alguna otra forma de obtener el token de metadatos de un campo (y, por lo tanto, su FieldInfo
) desde MemberRef?
Aquí hay un script del programa LINQPad que reproduce el problema. Es bastante grande, hay un montón de repetitivo.
void Main()
{
Init();
var obj =
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
var usage = PropertyFieldUsage(obj.GetType());
usage.Dump();
}
private static Dictionary<int, System.Reflection.Emit.OpCode> OneByteOps;
private static Dictionary<int, System.Reflection.Emit.OpCode> TwoByteOps;
public static Dictionary<PropertyInfo, List<FieldInfo>> PropertyFieldUsage(Type t)
{
var ret = new Dictionary<PropertyInfo, List<FieldInfo>>();
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetMethod != null);
var module = t.Module;
foreach (var prop in props)
{
var getMtd = prop.GetMethod;
var mtdBody = getMtd.GetMethodBody();
var il = mtdBody.GetILAsByteArray();
var fieldHandles = _GetFieldHandles(il);
var fieldInfos =
fieldHandles
.Select(
f => module.ResolveField(f)
).ToList();
ret[prop] = fieldInfos;
}
return ret;
}
// Define other methods and classes here
private static List<int> _GetFieldHandles(byte[] cil)
{
var ret = new List<int>();
int i = 0;
while (i < cil.Length)
{
int? fieldHandle;
System.Reflection.Emit.OpCode ignored;
var startsAt = i;
i += _ReadOp(cil, i, out fieldHandle, out ignored);
if (fieldHandle.HasValue)
{
ret.Add(fieldHandle.Value);
}
}
return ret;
}
private static int _ReadOp(byte[] cil, int ix, out int? fieldHandle, out System.Reflection.Emit.OpCode opcode)
{
const byte ContinueOpcode = 0xFE;
int advance = 0;
byte first = cil[ix];
if (first == ContinueOpcode)
{
var next = cil[ix + 1];
opcode = TwoByteOps[next];
advance += 2;
}
else
{
opcode = OneByteOps[first];
advance++;
}
fieldHandle = _ReadFieldOperands(opcode, cil, ix, ix + advance, ref advance);
return advance;
}
private static int? _ReadFieldOperands(System.Reflection.Emit.OpCode op, byte[] cil, int instrStart, int operandStart, ref int advance)
{
Func<int, int> readInt = (at) => cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24);
switch (op.OperandType)
{
case System.Reflection.Emit.OperandType.InlineBrTarget:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineSwitch:
advance += 4;
var len = readInt(operandStart);
var offset1 = instrStart + len * 4;
for (var i = 0; i < len; i++)
{
advance += 4;
}
return null;
case System.Reflection.Emit.OperandType.ShortInlineBrTarget:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.InlineField:
advance += 4;
var field = readInt(operandStart);
return field;
case System.Reflection.Emit.OperandType.InlineTok:
case System.Reflection.Emit.OperandType.InlineType:
case System.Reflection.Emit.OperandType.InlineMethod:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI8:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineNone:
return null;
case System.Reflection.Emit.OperandType.InlineR:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineSig:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineString:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineVar:
advance += 2;
return null;
case System.Reflection.Emit.OperandType.ShortInlineI:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.ShortInlineR:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.ShortInlineVar:
advance += 1;
return null;
default: throw new Exception("Unexpected operand type [" + op.OperandType + "]");
}
}
static void Init()
{
var oneByte = new List<System.Reflection.Emit.OpCode>();
var twoByte = new List<System.Reflection.Emit.OpCode>();
foreach (var field in typeof(System.Reflection.Emit.OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
{
var op = (System.Reflection.Emit.OpCode)field.GetValue(null);
if (op.Size == 1)
{
oneByte.Add(op);
continue;
}
if (op.Size == 2)
{
twoByte.Add(op);
continue;
}
throw new Exception("Unexpected op size for " + op);
}
OneByteOps = oneByte.ToDictionary(d => (int)d.Value, d => d);
TwoByteOps = twoByte.ToDictionary(d => (int)(d.Value & 0xFF), d => d);
}
El truco aquí es que es un tipo genérico (el segundo parámetro para ResolveField
), y sabemos que el getter no es un método genérico (el parámetro final para ResolveField
), por lo que necesita usar ResolveField
manera:
var obj = new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
Parse(obj, "A");
Parse(obj, "B");
static void Parse(object obj, string property)
{
var blob = obj.GetType().GetProperty(property).GetGetMethod()
.GetMethodBody().GetILAsByteArray();
// hard-code that we know the token is at offset 2
int token = BitConverter.ToInt32(blob, 2);
var field = obj.GetType().Module.ResolveField(token,
obj.GetType().GetGenericArguments(), null);
Console.WriteLine(field.Name);
Console.WriteLine(field.MetadataToken);
}
En el caso más general, cuando no sabe tanto sobre el tipo (es decir, que podría ser un tipo no genérico) y el método (aunque estrictamente hablando, los accesores de propiedades nunca son genéricos por derecho propio, pero esto demuestra uso amplio):
static MemberInfo ResolveMember(this MethodInfo method, int metadataToken)
{
Type type = method.DeclaringType;
Type[] typeArgs = null, methodArgs = null;
if (type.IsGenericType || type.IsGenericTypeDefinition)
typeArgs = type.GetGenericArguments();
if (method.IsGenericMethod || method.IsGenericMethodDefinition)
methodArgs = method.GetGenericArguments();
return type.Module.ResolveMember(metadataToken, typeArgs, methodArgs);
}