C
C#7mo ago
Hugh

Using ILGenerator to create and populate a List<string>

I'm currently trying to figure out the ILGenerator procedure to add items to a List. Using www.sharplab.io I can see that this code:
public class C {
public List<String> MyStringList { get; set; } = new List<string>{"a", "b"};
}
public class C {
public List<String> MyStringList { get; set; } = new List<string>{"a", "b"};
}
Generates a constructor that looks like this:
IL_0000: ldarg.0
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<string>::.ctor()
IL_0006: dup
IL_0007: ldstr "a"
IL_000c: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_0011: dup
IL_0012: ldstr "b"
IL_0017: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_001c: stfld class [System.Collections]System.Collections.Generic.List`1<string> C::'<MyStringList>k__BackingField'
IL_0021: ldarg.0
IL_0022: call instance void [System.Runtime]System.Object::.ctor()
IL_0027: ret
IL_0000: ldarg.0
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<string>::.ctor()
IL_0006: dup
IL_0007: ldstr "a"
IL_000c: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_0011: dup
IL_0012: ldstr "b"
IL_0017: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_001c: stfld class [System.Collections]System.Collections.Generic.List`1<string> C::'<MyStringList>k__BackingField'
IL_0021: ldarg.0
IL_0022: call instance void [System.Runtime]System.Object::.ctor()
IL_0027: ret
I'm trying to replicate the setting of MyStringList in my own custom constructor, and I'm getting an error saying that it's an invalid program. Here's my constructor:
Type listType = typeof(List<string>);
ConstructorInfo? listConstructor = listType.GetConstructor(Array.Empty<Type>());
MethodInfo? addMethod = listType.GetMethod("Add");

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, listConstructor);
// Start set list members
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldstr, "a");
il.Emit(OpCodes.Callvirt, addMethod); // I think that the issue is with this line and the same one 3 below
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldstr, "b");
il.Emit(OpCodes.Callvirt, addMethod);
// End set list members
il.Emit(OpCodes.Stfld, fieldBuilder);

ConstructorInfo parentConst = parentType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Array.Empty<Type>(), null) ?? throw new Exception();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, parentConst);
il.Emit(OpCodes.Ret);
Type listType = typeof(List<string>);
ConstructorInfo? listConstructor = listType.GetConstructor(Array.Empty<Type>());
MethodInfo? addMethod = listType.GetMethod("Add");

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, listConstructor);
// Start set list members
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldstr, "a");
il.Emit(OpCodes.Callvirt, addMethod); // I think that the issue is with this line and the same one 3 below
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldstr, "b");
il.Emit(OpCodes.Callvirt, addMethod);
// End set list members
il.Emit(OpCodes.Stfld, fieldBuilder);

ConstructorInfo parentConst = parentType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Array.Empty<Type>(), null) ?? throw new Exception();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, parentConst);
il.Emit(OpCodes.Ret);
1 Reply
Hugh
Hugh7mo ago
I've marked where I think the error is. If I'm creating an empty list, and I skip the code between the set list members comments, it works fine. My feeling is that the issue is something to do with the (!0) at the end. I'm not sure what I would do to make this. I've tried changing the potential issue lines to:
il.EmitCall(OpCodes.Callvirt, addMethod, null);
il.EmitCall(OpCodes.Callvirt, addMethod, null);
which does the same thing as just doing il.Call. I've also tried passing in an array of a single typeof(string) instead of null, but in that situation it says Calling convention must be VarArgs Any idea how I can re-create this IL dynamically? Looking into it a little further, I believe that the !0 in the parameter type list for Add just refers to it being the first generic type argument (i.e. string). A different IL viewer shows that line as:
IL_00a8: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0/*string*/)
IL_00a8: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0/*string*/)