Search code examples
c#cilmono.cecil

Generating enumerator.Current instead of (int)((List<T>.Enumerator*)(byte*)enumerator)->Current


I am currently working on an API.

Whenever the user marks a type with a certain attribute, I want to create a new List<int> field and loop through it, performing some operations.

Here is some relevant code:

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

Here is the relevant output:

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

List<int>.Enumerator enumerator = Numbers.GetEnumerator(); is my desired result. However, int value = (int)((List<T>.Enumerator*)(byte*)enumerator)->Current; is obviously not what I want.

What should I do differently so that my output becomes int value = enumerator.Current, instead of the unreadable mess that it currently is?


Solution

  • List<T>.Enumerator is a value type. As such, you need to be calling methods on the address of the enumerator variable, rather than on its value.

    You also can't use callvirt on value types (although you can do constrained virtual calls, which is useful for calling some methods from object). You need to use call here. This isn't a problem, because value types can't be subclassed, so you know the exact method you're calling.

    Therefore you need to:

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

    This explains why you're getting the strange decompiled output: the decompiler knows that Current can only be called on the address of enumerator, but it also sees that you're actually calling it on the value, so it concocts the cast to turn enumerator into a pointer to a List<T>.Enumerator.

    You can see that on SharpLab.