Генерация перечислителя. Текущий вместо (int) ((Список <T>.Enumerator *) (байт *) перечислителя) -> Текущий - PullRequest
0 голосов
/ 10 января 2020

В настоящее время я работаю над API.

Всякий раз, когда пользователь отмечает тип с определенным атрибутом, я хочу создать новое поле List<int> и l oop через него, выполняя некоторые операции.

Вот соответствующий код:

TypeReference intTypeReference = moduleDefinition.ImportReference(typeof(int));

TypeReference listType = moduleDefinition.ImportReference(typeof(List<>));
GenericInstanceType intListType = listType.MakeGenericInstanceType(intTypeReference);

var numberList = 
    new FieldDefinition(
        name: "Numbers", 
        attributes: field.Attributes,
        fieldType: moduleDefinition.ImportReference(intListType));
generatedType.Fields.Add(numberList);

Type enumeratorType = typeof(List<>.Enumerator);

var enumeratorTypeReference = moduleDefinition.ImportReference(enumeratorType);
GenericInstanceType intEnumeratorType = enumeratorTypeReference.MakeGenericInstanceType(intTypeReference);

var enumeratorVariable = new VariableDefinition(intEnumeratorType);
convertMethod.Body.Variables.Add(enumeratorVariable);

ilProcessor.Emit(OpCodes.Ldarg_0); // this
ilProcessor.Emit(OpCodes.Ldfld, numberList); 

MethodReference getEnumeratorMethodReference =
    new MethodReference(
        name: "GetEnumerator",
        returnType: intEnumeratorType,
        declaringType: intListType)
    {
        HasThis = true
    };
ilProcessor.Emit(OpCodes.Callvirt, getEnumeratorMethodReference);
ilProcessor.Emit(OpCodes.Stloc, enumeratorVariable);

TypeDefinition enumeratorTypeDefinition = enumeratorTypeReference.Resolve();
MethodDefinition getCurrentMethod =
    enumeratorTypeDefinition.Properties.Single(p => p.Name == "Current").GetMethod;
MethodDefinition moveNextMethod = 
    enumeratorTypeDefinition.Methods.Single(m => m.Name == "MoveNext");

MethodReference getCurrentMethodReference = moduleDefinition.ImportReference(getCurrentMethod);
MethodReference moveNextMethodReference = moduleDefinition.ImportReference(moveNextMethod);

// Call enumerator.Current
ilProcessor.Emit(OpCodes.Ldloc, enumeratorVariable);
ilProcessor.Emit(OpCodes.Callvirt, getCurrentMethodReference);
// Store it inside currentVariable
ilProcessor.Emit(OpCodes.Stloc, currentVariable);
ilProcessor.Emit(OpCodes.Nop);

Вот соответствующий вывод:

List<int>.Enumerator enumerator = Numbers.GetEnumerator();
int value = (int)((List<T>.Enumerator*)(byte*)enumerator)->Current;

List<int>.Enumerator enumerator = Numbers.GetEnumerator(); - мой желаемый результат. Тем не менее, int value = (int)((List<T>.Enumerator*)(byte*)enumerator)->Current; явно не то, что я хочу.

Что я должен сделать по-другому, чтобы мой вывод стал int value = enumerator.Current вместо нечитаемого беспорядка, которым он является в настоящее время?

1 Ответ

1 голос
/ 10 января 2020

List<T>.Enumerator является типом значения. Таким образом, вам необходимо вызывать методы для адреса переменной enumerator, а не для ее значения.

Вы также не можете использовать callvirt для типов значений ( хотя вы можете делать ограниченные виртуальные вызовы, что полезно для вызова некоторых методов из object). Вам нужно использовать call здесь. Это не проблема, поскольку типы значений не могут быть разделены на подклассы, поэтому вы точно знаете, какой метод вызываете.

Поэтому вам необходимо:

ilProcessor.Emit(OpCodes.Ldloca_S, enumeratorVariable);
ilProcessor.Emit(OpCodes.Call, getCurrentMethodReference);

Это объясняет, почему вы получаете странный декомпилированный вывод: декомпилятор знает, что Current может быть вызван только по адресу enumerator, но он также видит, что вы на самом деле вызываете его по значению, поэтому он придумывает приведение к повороту enumerator в указатель на List<T>.Enumerator.

Вы можете видеть это на SharpLab .

...