Skip to main content

Emitting Dynamic Method with 'in' Parameter

Haoyu JiaAbout 627 wordsAbout 2 minCSharpEmitDynamic MethodDynamic Code Generation

This is a delegate with ref keyword on one of its parameters:

public delegate void FooByRef(ref int number);

To generate a dynamic method matching this delegate, we can use code like this:

var dynamicFooByRef = typeBuilder.DefineMethod("DynamicFooByRef",
            MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
            CallingConventions.Standard,
            typeof(void), [typeof(int).MakeByRefType()]);

The key is to invoke .MakeByRefType() on parameter of int in the parameters list to replace it with ref int.

Now let's consider another delegate, whose parameter has a in keyword:

public delegate void FooByIn(in int number);

Then, the dynamic method generated by dynamicFooByRef cannot be casted into delegate FooByIn. By inspecting the reflection information from MethodInfo and ParameterInfo, we can see that the IsIn property of parameters with in keyword is true; also, they have attributes of InAttribute and IsReadOnlyAttribute. We can use following code to make the reflection information of our dynamic methods to look exactly the same to the ones generated by compiler:

var dynamicFooByIn = typeBuilder.DefineMethod("DynamicFooByRef",
            MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
            CallingConventions.Standard,
            typeof(void), [typeof(int).MakeByRefType()]);
var parameterNumber = dynamicFooByIn.DefineParameter(1, ParameterAttributes.In, "number");
parameterNumber.SetCustomAttribute(new CustomAttributeBuilder(
        typeof(InAttribute).GetConstructor(Type.EmptyTypes)!, []));
parameterNumber.SetCustomAttribute(new CustomAttributeBuilder(
        typeof(IsReadOnlyAttribute).GetConstructor(Type.EmptyTypes)!, []));

Here, DefineParameter(...) is used to define metadata of parameters, such as the name and attributes. Then we invoked SetCustomAttribute(...) method on the return value of DefineParameter(...), which is an instance of ParameterBuilder, to manually add attributes to the parameter.

So far, the reflection information of our dynamic method dynamicFooByIn has the same metadata with methods generated by compiler, but dynamicFooByIn still cannot be converted into delegate FooByIn.

This is because that DefineParameter(...) will only effect the metadata in the reflection information, which is MethodInfo and ParameterInfo, the IL generated by dynamicFooByIn is still different with methods generated by compiler. The difference is [in] string& modreq ([System.Runtime]System.Runtime.InteropServices.InAttribute) number in the parameters list of IL. We need to add modreq ([System.Runtime]System.Runtime.InteropServices.InAttribute) into the IL of our dynamic method.

modreq represents 'required (custom) modifier'. Here is the description about modreq in the CLI specification:

Custom modifiers, defined using modreq (“required modifier”) and modopt (“optional modifier”), are similar to custom attributes (§II.21) except that modifiers are part of a signature rather than being attached to a declaration. Each modifer associates a type reference with an item in the signature. For example, the const qualifier in the C programming language can be modelled with an optional modifier since the caller of a method that has a const-qualified parameter need not treat it in any special way. On the other hand, a parameter that shall be copy-constructed in C++ shall be marked with a required custom attribute since it is the caller who makes the copy.

In the method DefineMethod, there are parameters for us to use to define required and optional custom modifiers for parameters. So the final code goes as follows:

var dynamicFooByIn = typeBuilder.DefineMethod("DynamicFooByRef",
            MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
            CallingConventions.Standard,
            typeof(void), null, null,
            [typeof(int).MakeByRefType()], [[typeof(InAttribute)]], null);
var parameterNumber = dynamicFooByIn.DefineParameter(1, ParameterAttributes.In, "number");
parameterNumber.SetCustomAttribute(new CustomAttributeBuilder(
        typeof(InAttribute).GetConstructor(Type.EmptyTypes)!, []));
parameterNumber.SetCustomAttribute(new CustomAttributeBuilder(
        typeof(IsReadOnlyAttribute).GetConstructor(Type.EmptyTypes)!, []));

Each element in this array [[typeof(InAttribute)]] is the array of modifiers for one parameter. Since only InAttribute appears in the required modifiers list, we only add it into the modifiers array for parameter 'number'. Note that if we remove the code after DefineMethod, which includes DefineParameter(...) and SetCustomAttribute(...), then our generated dynamic method still can be converted into delegate FooByIn, and the reason has been discussed above: they only affect the reflection information and is not involved in the runtime type check.

Last update: