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).