Search code examples
c#reflectiondynamicmethodilgenerator

How to call Action<string, bool> from il generator


In this example code i am trying to invoke a anonymous action from il generator. I am not sure if and how i can load the reference to the delegate and how to call it. I can do it if the OnFunctionCall is a static method not property.

public delegate void TestDelegate();

public static class ExampleOne
{
    public static Action<string, bool> OnFunctionCall
        => (message, flag) => Console.WriteLine("Example");
}

public static class ExampleTwo
{
    public static TType CreateDelegate<TType>(Action<string, bool> onFunctionCall)
        where TType : class
    {
        var method = new DynamicMethod($"{Guid.NewGuid()}", typeof(void), Type.EmptyTypes, typeof(TType), true);

        ILGenerator il = method.GetILGenerator();

        // Emit some code that invoke unmanaged function ...

        // loading the first string argument
        il.Emit(OpCodes.Ldstr, method.Name);

        // not sure here how to load boolean value to the stack
        il.Emit(OpCodes.Ldc_I4_0);

        // this line doesn't work
        // example two has no idea about ExampleOne
        // is it possible to load the reference of the Action<string, bool> to the stack and call it ?
        il.Emit(OpCodes.Call, onFunctionCall.Method);

        il.Emit(OpCodes.Ret);

        return method.CreateDelegate(typeof(TestDelegate)) as TType;
    }
}

public class Program
{
    public static void Main(string[] args)
        => ExampleTwo
            .CreateDelegate<TestDelegate>(ExampleOne.OnFunctionCall)
            .Invoke();
}

Solution

  • You have to pass the information where the delegate you want to invoke is stored. The convenience way is to accept a MemberExpression, otherwise accepting a MemberInfowould work too. Have a look at your modified code:

    public delegate void TestDelegate();
    
    public static class ExampleOne
    {
        public static Action<string, bool> OnFunctionCall
            => (message, flag) => Console.WriteLine("OnFunctionCall");
    
        public static Action<string, bool> OnFunctionCallField 
            = (message, flag) => Console.WriteLine("OnFunctionCallField");
    }
    
    public static class ExampleTwo
    {
        public static TType CreateDelegate<TType>(Expression<Func<object>> expression)
            where TType : class
        {
            var body = expression.Body as MemberExpression;
            if (body == null)
            {
                throw new ArgumentException(nameof(expression));
            }
    
            var method = new DynamicMethod($"{Guid.NewGuid()}", typeof(void), Type.EmptyTypes, typeof(TType), true);
    
            ILGenerator il = method.GetILGenerator();
    
            // Get typed invoke method and
            // call getter or load field
            MethodInfo invoke;
            if (body.Member is PropertyInfo pi)
            {
                invoke = pi.PropertyType.GetMethod("Invoke");
                il.Emit(OpCodes.Call, pi.GetGetMethod());
            }
            else if (body.Member is FieldInfo fi)
            {
                invoke = fi.FieldType.GetMethod("Invoke");
                il.Emit(OpCodes.Ldsfld, fi);
            }
            else
            {
                throw new ArgumentException(nameof(expression));
            }
    
            il.Emit(OpCodes.Ldstr, method.Name);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Callvirt, invoke);
            il.Emit(OpCodes.Ret);
    
            return method.CreateDelegate(typeof(TestDelegate)) as TType;
        }
    }
    
    public class Program
    {
        public static void Main(string[] args)
        {
            ExampleTwo
                .CreateDelegate<TestDelegate>(() => ExampleOne.OnFunctionCall)
                .Invoke();
    
            ExampleTwo
                .CreateDelegate<TestDelegate>(() => ExampleOne.OnFunctionCallField)
                .Invoke();
    
            Console.ReadLine();
        }
    }
    

    The code is running on .Net Core 2.0.