3

This is a kind of follow-on to a prior thread. I'm building a small wrapper to do upcalls to dynamically typed methods provided by my users. The scheme works well... but only for static methods. Although CreateDelegate is supposed to work for instance methods too, when used with those, it throws a "binding error" if the method isStatic flag is false (actually, because I have the throw-on-error flag is false, it returns null). Here's a code sample in which you can see this happen.

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 

namespace ConsoleApplication4 
{ 
    delegate void myFoo(int i, string s);
    delegate void myNull();

    internal class Callable
    {
        internal int nParams;
        internal Type[] ptypes;
        internal Delegate cb;
        internal static Type[] actions = { typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>), typeof(Action<,,,,>), 
                                         typeof(Action<,,,,,>), typeof(Action<,,,,,,>), typeof(Action<,,,,,,,>), typeof(Action<,,,,,,,,>), typeof(Action<,,,,,,,,,>), 
                                         typeof(Action<,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,,>), typeof(Action<,,,,,,,,,,,,,,>) };

        internal Callable(Delegate hisCb)
        {
            MethodInfo mi = hisCb.Method;
            ParameterInfo[] pi = mi.GetParameters();
            ptypes = pi.Select(p => p.ParameterType).ToArray();
            nParams = ptypes.Length;
            if (nParams > 0 && nParams < 17)
            {
                cb = Delegate.CreateDelegate(actions[nParams].MakeGenericType(ptypes), mi, false);
                if (cb == null)
                    Console.WriteLine("Warning: Unsuccessful attempt to CreateDelegate for " + hisCb + ", with methodinfo " + mi);
                else
                    Console.WriteLine("Successful attempt to CreateDelegate for " + hisCb + ", with methodinfo " + mi);
            }
            else
                cb = hisCb;
        }

        internal void doUpcall(object[] args)
        {
            if (args.Length != nParams)
                 throw new ArgumentException("Argument count must match number of parameters");
            switch (nParams)
            {
                case 1:
                    ((dynamic)cb).Invoke((dynamic)args[0]);
                    break;
                case 2:
                    ((dynamic)cb).Invoke((dynamic)args[0], (dynamic)args[1]);
                    break;
                    // ... cases 3-15 similar, omitted to save space
                default:
                    cb.DynamicInvoke((dynamic)args);
                    break;
            }
        }
    }

    internal class FooBar
    {
        internal FooBar()
        {
        }

        internal static void printFields(int i, string s)
        {
            Console.WriteLine("In FooBar.printField-s with i="+i+", s="+s);
        }

        internal void printFieldi(int i, string s)
        {
            Console.WriteLine("In FooBar.printField-i with i=" + i + ", s=" + s);
        }
    }

    internal class Program 
    { 
        private static void Main(string[] args) 
        {
            FooBar myFooBar = new FooBar();
            Callable cbfb0 = new Callable((myFoo)FooBar.printFields);
            cbfb0.doUpcall(new object[] { 77, "myfb" });
            Callable cbfb1 = new Callable((myFoo)myFooBar.printFieldi);
            cbfb1.doUpcall(new object[] { 77, "myfb" });
            string pc = "Main";
            Callable cb0 = new Callable((myNull)delegate() { Console.WriteLine("Hello from myNull"); });
            cb0.doUpcall(new object[0]);
            Callable cb1 = new Callable((myFoo)delegate(int i, string s) { Console.WriteLine("i=" + i + ", s.Length = " + s.Length); });
            Console.WriteLine("About to attempt to call Foo: Good args");
            cb1.doUpcall(new object[] { 2, "bar" });
            Console.WriteLine("After calling Foo");
            Callable cb2 = new Callable((myFoo)delegate(int i, string s) { Console.WriteLine("My parent class is " + pc + ", i=" + i + ", s.Length = " + s.Length); });
            Console.WriteLine("About to attempt to call Foo: Good args");
            cb2.doUpcall(new object[] { 12, "Bar" });
            Console.WriteLine("After calling Foo");
            System.Threading.Thread.Sleep(15000);
        } 

        private static void Foo(int i, string s) 
        { 
            Console.WriteLine("i=" + i + ", s.Length = " + s.Length); 
        } 
    } 
}

Can anyone help me understand why CreateDelegate is behaving this way? The C# and .NET reference information says it should work for both static and instance methods. If you break on the "unsuccessful" case you can confirm that the thing determining success or failure is the value of the mi.isStatic flag.

PS: Note the use of (dynamic) to cast the arguments to the required types at runtime! This, I think, is way cool. Used to be impossible -- you want to do a cast (T) but don't know what the type T will be and hence can create an object of type T, but can't do a dynamic call to a method using that object, as in my 15 case statements. By casting to (dynamic) I avoid that problem -- solves an issue for which there seem to be dozens of old threads that were left unresolved! (And this improves on the code suggested in the prior thread... which had that same issue of casting using known types).

Community
  • 1
  • 1
Ken Birman
  • 1,088
  • 8
  • 25
  • 1
    Tip: you can use `Expression.GetActionType(ptypes)` to get the delegate type. – Balazs Tihanyi Mar 24 '12 at 20:54
  • Ok, but would still need the switch statement and the 15 cases, right? The array of actions is ugly; I'll make this change. But the big switch statement is more annoying in my view! Still, this works, and it completely fixes my issue (which was that user exceptions were unwinding the call stack up to the .Invoke if the debugger wasn't running in first-chance mode...). So I'm a happy camper, even with the ugly switch statement in my code! – Ken Birman Mar 24 '12 at 21:10

2 Answers2

5

CreateDelegate creates a callable delegate - in order to do that, it must have the instance to call the method on. For static methods, no instance is required - you are calling the overload that allows creating a delegate to a static method.

To create a delegate on an instance method, you must use an overlaod that allows you to pass in the instance as well:

 cb = Delegate.CreateDelegate(actions[nParams].MakeGenericType(ptypes), 
                         hisCb.Target, 
                         mi, false);
Philip Rieck
  • 32,368
  • 11
  • 87
  • 99
1

The only call I see to Delegate.CreateDelegate is the one invoking the overload that takes Type, MethodInfo, bool. The documentation states that it

Creates a delegate of the specified type to represent the specified static method...

If you have an instance method, you have to call a different overload (one that takes an object) to create a delegate from it.

Gabe
  • 84,912
  • 12
  • 139
  • 238