Подтвердить что ты не робот

Получение поля токен метаданных MemberRef относится к

Справедливое предупреждение, это может быть немного эзотерическим и сложным.

Учитывая членство в MemberRef (более подробное объяснение ниже), извлеченное из потока CIL, как вы определяете, какое поле, если оно есть, указывает (и получает FieldInfo для него)?

Вот что я понял до сих пор

В соответствии с стандартом ECMA 335 MemberRef является токеном метаданных, который представляет собой в основном поиск в таблице, который может указывать либо на поле токен метаданных или токен метаданных метода. Любое начало токена метаданных 0x0A является MemberRef.

enter image description here

Раньше я не встречал ни одного из них, но они не кажутся такими необычными. Я смог получить один из них, используя следующий анонимный тип в методе:

new
{
    A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
    B = (DateTime?)null
}

Когда я захватываю тело метода через отражение (получаем PropertyInfo, получаем GetMethod, получите MethodBody, а затем получить IL) A get метод:

[2, 123, 79, 0, 0, 10, 42]

Что преобразуется в:

ldarg.0
ldfld 0x0A00004F
ret

Если я задумываюсь и получаю фоновое поле (полагаясь на подобие имени на выбор <A>i__Field, ничего не алгоритмическое), я вижу, что MetadataToken is 0x04000056.

Обратите внимание, что генерируемые токены могут различаться между компиляциями.

Маркер, начинающийся с 0x04, является полем: enter image description here

В большинстве случаев (для всех не анонимных объектов в моем ограниченном тестировании) IL содержит маркер метаданных поля. Это легко превратить в FieldInfo через Module.ResolveField(int), выясняя, что делать с членом MemberRef, отключает меня.

Прокручивая другие методы ResolveXXX на Module, единственное, что может сделать что-либо с MemberRef, это ResolveSignature. При запуске на вышеприведенном MemberRef он возвращает массив [6, 19, 0]. Я не знаю, что с этим делать.

Код, над которым я работаю, не закончен, но открыт. Ошибка можно увидеть, выполнив этот тест, в результате чего возникает исключение при сбое поиска в поле эта строка. Обратите внимание, что сам тест не завершен, он не ожидается, что он достигнет успеха, но он также не должен умереть.

Кто-нибудь знает, что делать с этой сигнатурой или каким-либо другим способом получить к токену метаданных поля (и, следовательно, его FieldInfo) из MemberRef?

Здесь программа LINQPad script, которая воспроизводит проблему. Он довольно большой, там много шаблонов.

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);
}
4b9b3361

Ответ 1

Трюк заключается в том, что это общий тип (второй параметр ResolveField), и мы знаем, что геттер не является общим методом (конечный параметр ResolveField), поэтому вам нужно использовать ResolveField так:

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);
}

В более общем случае, когда вы не знаете столько о типе (т.е. что это может быть не общий тип) и метод (хотя, строго говоря, аксессоры свойств никогда не являются универсальными по своему усмотрению, но это показывает широкое использование):

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);
}