ILGenerator: How to generate a Func<> to pass as an argument

191 Views Asked by At

I am attempting to create a proxy wrapper around services that are defined by an interface to "hide" the client specific calling code from the developer user of the service.

I have already been able to generate most of the wrapper class itself but am now struggling on generating the Func argument to the pre-existing client call.

An example TService has an interface:

public interface ITestService
{
    public Task ServiceMethodAsync();
    public Task<TestDTO> ServiceMethodWithResultAsync();
}

And the signature for the client implementation is:

public Task<TResult> CallAsync<TResult>(Func<TService, Task<TResult>> operation);
public Task CallAsync(Func<TService, Task> operation);

When calling directly a developer currently writes something like:

var result = await client.CallAsync(service => service.ServiceMethodWithResultAsync(requestDTO));

While the preferred option would be that they could just call:

var result = await service.ServiceMethodWithResultAsync(requestDTO);

as this allows the either a local service or my remote wrapper to be injected by DI using the IService interface and whether it is a remote service or local service does not need considered by the developer.

I currently have my typebuilder generating the required code to create the wrapper class inheriting from the client baseType and defining all interface methods against it, but currently passing a null as the Func<TService, Task<TResult>> operation.

private static void DefineInterface(TypeBuilder typeBuilder, Type baseType, Type serviceInterfaceType)
{
    foreach (MethodInfo serviceMethod in serviceInterfaceType.GetMethods())
    {
        ParameterInfo[] parameters = serviceMethod.GetParameters();
        MethodBuilder methodBuilder = typeBuilder.DefineMethod(
            serviceMethod.Name,
            serviceMethod.Attributes ^ MethodAttributes.Abstract,
            serviceMethod.CallingConvention,
            serviceMethod.ReturnType,
            serviceMethod.ReturnParameter.GetRequiredCustomModifiers(),
            serviceMethod.ReturnParameter.GetOptionalCustomModifiers(),
            parameters.Select(p => p.ParameterType).ToArray(),
            parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(),
            parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray());

        ILGenerator ilGenerator = methodBuilder.GetILGenerator();

        MethodInfo callAsyncMethod = getCallAsyncMethod(baseType, serviceMethod);

        ilGenerator.Emit(OpCodes.Ldarg_0); // Push "this"
        ilGenerator.Emit(OpCodes.Ldnull); // Push Func<> argument
        ilGenerator.Emit(OpCodes.Call, callAsyncMethod); // Call CallAsync on the base client
        ilGenerator.Emit(OpCodes.Ret);
    }
}

From here I am a little stuck on exactly how to go about defining the Func<> service => service.ServiceMethodWithResultAsync(requestDTO) including the requestDTO parameter(s) that I expect/assume will be in Ldarg_1, Ldarg_2 etc. ?

Any and all help greatly appreciated.

0

There are 0 best solutions below