views:

1372

answers:

3

I suppose in some ways either (or both) Delegate or MethodInfo qualify for this title. However, neither provide the syntactic niceness that I'm looking for. So, in short, Is there some way that I can write the following:

FunctionPointer foo = // whatever, create the function pointer using mechanisms
foo();

I can't use a solid delegate (ie, using the delegate keyword to declare a delegate type) because there is no way of knowing till runtime the exact parameter list. For reference, here's what I've been toying with in LINQPad currently, where B will be (mostly) user generated code, and so will Main, and hence for nicety to my users, I'm trying to remove the .Call:

void Main()
{
    A foo = new B();
    foo["SomeFuntion"].Call();
}

// Define other methods and classes here
interface IFunction {
    void Call();
    void Call(params object[] parameters);
}

class A {
    private class Function : IFunction {
     private MethodInfo _mi;
     private A _this;
     public Function(A @this, MethodInfo mi) {
      _mi = mi;
      _this = @this;
     }

     public void Call() { Call(null); }
     public void Call(params object[] parameters) {
      _mi.Invoke(_this, parameters);
     }
    }

    Dictionary<string, MethodInfo> functions = new Dictionary<string, MethodInfo>();

    public A() {
     List<MethodInfo> ml = new List<MethodInfo>(this.GetType().GetMethods());
     foreach (MethodInfo mi in typeof(Object).GetMethods())
     {
      for (int i = 0; i < ml.Count; i++)
      {
       if (ml[i].Name == mi.Name)
        ml.RemoveAt(i);
      }
     }

     foreach (MethodInfo mi in ml)
     {
      functions[mi.Name] = mi;
     }
    }

    public IFunction this[string function] {
     get { 
      if (!functions.ContainsKey(function))
       throw new ArgumentException();

      return new Function(this, functions[function]);
     }
    }
}

sealed class B : A {
    public void SomeFuntion() {
     Console.WriteLine("SomeFunction called.");
    }
}
+17  A: 

You say you want to keep the number and type of parameters open, but you can do that with a delgate:

public delegate object DynamicFunc(params object[] parameters);

This is exactly the same thing you currently have. Try this:

class Program
{
    static void Main(string[] args)
    {
        DynamicFunc f = par =>
                        {
                            foreach (var p in par)
                                Console.WriteLine(p);

                            return null;
                        };

        f(1, 4, "Hi");
    }
}

You can think of an instance-method delegate as very similar to your Function class: an object an a MethodInfo. So there's no need to rewrite it.

Also function pointers in C and C++ are not any closer to what you need: they cannot be bound to an object instance and function, and also they are statically typed, not dynamically typed.

If you want to "wrap" any other method in a DynamicFunc delegate, try this:

public static DynamicFunc MakeDynamicFunc(object target, MethodInfo method)
{
    return par => method.Invoke(target, par);
}

public static void Foo(string s, int n)    
{
    Console.WriteLine(s);
    Console.WriteLine(n);
}

and then:

DynamicFunc f2 = MakeDynamicFunc(null, typeof(Program).GetMethod("Foo"));

f2("test", 100);

Note that I'm using a static method Foo so I pass null for the instance, but if it was an instance method, I'd be passing the object to bind to. Program happens to be the class my static methods are defined in.

Of course, if you pass the wrong argument types then you get errors at runtime. I'd probably look for a way to design your program so that as much type information is captured at compile time as possible.

Daniel Earwicker
While I consider you a genius for thinking of this, I'm getting bind errors when I try to create the delegate with: return (DynamicFunction)Delegate.CreateDelegate(typeof(DynamicFunction), this, functions[function]);
Matthew Scharley
That's a lot more complex... hang on.
Daniel Earwicker
Okay, maybe not that complex... see update.
Daniel Earwicker
Compile time for the `B` class will be runtime, as I said, it's user-generated, so sadly, any errors there will have to handled by them (and there are good provisions, error reporting, etc, to do that).
Matthew Scharley
Fair enough - to paraphrase Einstein, a program should be as statically-typed as possible, but no statically-typed-er.
Daniel Earwicker
A: 

You look like someone who's going to appreciate C# 4.0's dynamic, which looks to make all this infrastructure redundant. I don't know what your shipping schedule is, but it's almost exactly what you want...

However, if both Main and B will be user-generated - why exactly do you need such a dynamic infrastructure in the first place? You could simply use the built-in compiler, right? http://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx

Eamon Nerbonne
I'm using CSharpCodeProvider to compile user scripts. So Main is user generated, I compile it, then run it ever so often as part of my program. As for shipping schedule, for the moment, this is a personal project, so... there isn't really one.
Matthew Scharley
The reason why it needs to be so loose is because `Main`, `A` and `B` will be in three different assemblies by the time all is said and done. (long story)
Matthew Scharley
+1  A: 

Here's another bit of code you could use; Reflection is rather slow, so if you expect your Dynamic function calls to be called frequently, you don't want method.Invoke inside the delegate:

public delegate void DynamicAction(params object[] parameters);
static class DynamicActionBuilder
{
    public static void PerformAction0(Action a, object[] pars) { a(); }
    public static void PerformAction1<T1>(Action<T1> a, object[] p) {
        a((T1)p[0]);
    }
    public static void PerformAction2<T1, T2>(Action<T1, T2> a, object[] p) {
        a((T1)p[0], (T2)p[1]);
    }
    //etc...

    public static DynamicAction MakeAction(object target, MethodInfo mi) {
        Type[] typeArgs =
            mi.GetParameters().Select(pi => pi.ParameterType).ToArray();
        string perfActName = "PerformAction" + typeArgs.Length;
        MethodInfo performAction =
            typeof(DynamicActionBuilder).GetMethod(perfActName);
        if (typeArgs.Length != 0)
            performAction = performAction.MakeGenericMethod(typeArgs);
        Type actionType = performAction.GetParameters()[0].ParameterType;
        Delegate action = Delegate.CreateDelegate(actionType, target, mi);
        return (DynamicAction)Delegate.CreateDelegate(
            typeof(DynamicAction), action, performAction);
    }
}

And you could use it like this:

static class TestDab
{
    public static void PrintTwo(int a, int b) {
        Console.WriteLine("{0} {1}", a, b);
        Trace.WriteLine(string.Format("{0} {1}", a, b));//for immediate window.
    }
    public static void PrintHelloWorld() {
        Console.WriteLine("Hello World!");
        Trace.WriteLine("Hello World!");//for immediate window.
    }

    public static void TestIt() {
        var dynFunc = DynamicActionBuilder.MakeAction(null,
            typeof(TestDab).GetMethod("PrintTwo"));
        dynFunc(3, 4);
        var dynFunc2 = DynamicActionBuilder.MakeAction(null,
            typeof(TestDab).GetMethod("PrintHelloWorld"));
        dynFunc2("extraneous","params","allowed"); //you may want to check this.
    }
}

This will be quite a bit faster; each dynamic call will involve 1 typecheck per param, 2 delegate calls, and one array construction due to the params-style passing.

Eamon Nerbonne
I do seem to recall something about Invoke being slow and to use CreateDelegate instead, which is why I jumped on that solution to start with, since it's entirely possible these functions will be called a great many times per second... I'll look over the details of this when I'm not half asleep however (reads: 8 hours or so)
Matthew Scharley
Out of curiosity I timed this technique (with two parameters) against mine, and found that my method has an overhead of three and half **millionths of a second** per call. So I suspect the overhead is unlikely to be an issue in a real application; depends what kind of things your users will be doing in their functions.
Daniel Earwicker