5

Let's say I have a type, which I know to be derived from a Delegate type. I would like to create an object of this type wrapping an anonymous delegate that accepts arbitrary params and returns an object of correct return type:

var retType = type.GetMethod("Invoke").ReturnType;
var obj = Delegate.CreateDelegate(type, delegate(object[] args) {
    ...
    if (retType != typeof(void))
      ... somehow create object of type retType and return it ...
});

Obviously this won't compile, because CreateDelegate expects a MethodInfo as the second argument. How can I do this correctly?

Update: A little more info on what I am trying to achieve. There are two applications running - client in a browser and a server in C#. Browser is able to call remote functions on the server side by serializing arguments to JSON and sending the call over the network (like in RPC). This works already, but I would like to add support for callbacks. For example:

JavaScript (client):

function onNewObject(uuid) { console.log(uuid); }
server.notifyAboutNewObjects(onNewObject);

C# (server):

void notifyAboutNewObjects(Action<string> callback) {
  ...
  callback("new-object-uuid");
  ...
}

The middleware code will receive a call from the browser and will need to generate fake callback delegate that will actually send the call to callback back to the browser and block the thread until it completes. The code for sending/receiving is there already, I am just stuck on how to generate a generic delegate that will simply put all arguments into an array and pass them to the sending code.

Update: If someone can write code that will generate such a delegate at runtime (e.g. using DynamicMethod) , I'll consider that a valid answer. I just don't have enough time to learn how to do this and hope that someone experienced will be able to write this code quickly enough. Essentially the code should just take arbitrary delegate params (list and types are available at runtime), put them into an array and call generic method. The generic method will always return an object, which should be cast into respective return type or ignored if the function returns void.

Uppdate: I've created a small test program that demonstrates what I need:

using System;
using System.Reflection;

namespace TestDynamicDelegates
{
    class MainClass
    {
        // Test function, for which we need to create default parameters.
        private static string Foobar(float x, Action<int> a1, Func<string, string> a2) {
            a1(42);
            return a2("test");
        }

        // Delegate to represent generic function.
        private delegate object AnyFunc(params object[] args);

        // Construct a set of default parameters to be passed into a function.
        private static object[] ConstructParams(ParameterInfo[] paramInfos)
        {
            object[] methodParams = new object[paramInfos.Length];
            for (var i = 0; i < paramInfos.Length; i++) {
                ParameterInfo paramInfo = paramInfos[i];
                if (typeof(Delegate).IsAssignableFrom(paramInfo.ParameterType)) {
                    // For delegate types we create a delegate that maps onto a generic function.
                    Type retType = paramInfo.ParameterType.GetMethod("Invoke").ReturnType;

                    // Generic function that will simply print arguments and create default return value (or return null
                    // if return type is void).
                    AnyFunc tmpObj = delegate(object[] args) {
                        Console.WriteLine("Invoked dynamic delegate with following parameters:");
                        for (var j = 0; j < args.Length; j++)
                            Console.WriteLine("  {0}: {1}", j, args[j]);
                        if (retType != typeof(void))
                            return Activator.CreateInstance(retType);
                        return null;
                    };

                    // Convert generic function to the required delegate type.
                    methodParams[i] = /* somehow cast tmpObj into paramInfo.ParameterType */
                } else {
                    // For all other argument type we create a default value.
                    methodParams[i] = Activator.CreateInstance(paramInfo.ParameterType);
                }
            }

            return methodParams;
        }

        public static void Main(string[] args)
        {
            Delegate d = (Func<float, Action<int>,Func<string,string>,string>)Foobar;

            ParameterInfo[] paramInfo = d.Method.GetParameters();
            object[] methodParams = ConstructParams(paramInfo);
            Console.WriteLine("{0} returned: {1}", d.Method.Name, d.DynamicInvoke(methodParams));
        }
    }
}
Sergiy Belozorov
  • 5,856
  • 7
  • 40
  • 73
  • How you have defined `type`? – Hamlet Hakobyan Sep 08 '13 at 18:48
  • 7
    Delegates know how to build a stack activation frame to call a method with a *specific* known method signature. They don't know how to put the passed arguments in an array. This is intentional. You can't make this work. – Hans Passant Sep 08 '13 at 19:28
  • 1
    Have you considered Expression.GetActionType/GetFuncType ? – Marc Gravell Sep 08 '13 at 19:29
  • @HamletHakobyan: `Type type = ...;` – Sergiy Belozorov Sep 09 '13 at 10:01
  • @HansPassant: I would expect that at runtime, the reflection will analyze the respective delegate type and be able to extract the arguments from the stack and put them in an array instead. This may be slow, but should be possible nonetheless. – Sergiy Belozorov Sep 09 '13 at 10:02
  • @MarcGravell: Not sure what you mean. I do have the type. I just need to construct the delegate of this type that will put them into an array and call another delegate that accepts an `object[]`. – Sergiy Belozorov Sep 09 '13 at 10:05
  • If the signature and return type of the `type` delegate were known, you could just do e.g. `Func tmpObj = args => { ... };` followed by `var obj = Delegate.CreateDelegate(type, tmpObj.Method);`. But if you don't know the signature and return type, how will you fill out the body `{ ... }` in a meaningful way? – Jeppe Stig Nielsen Sep 09 '13 at 10:21
  • 2
    It looks like you need runtime code generation. – Alex F Sep 09 '13 at 10:34
  • @JeppeStigNielsen: Thanks for the idea, but this doesn't work. It compiles, but I get a runtime exception `ArgumentException` (Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.) when trying to use `Delegate.CreateDelegate`. – Sergiy Belozorov Sep 09 '13 at 13:54
  • @JeppeStigNielsen: Regarding the body - I serialize the arguments to JSON and send them over network to be executed on the other end. Return value is returned in a similar way. – Sergiy Belozorov Sep 09 '13 at 14:01
  • 2
    @SergiyByelozyorov Regarding "Cannot bind": That's why I said ***if*** the signature were known. – Jeppe Stig Nielsen Sep 09 '13 at 16:48
  • Just updated the question with more details. Perhaps this helps finding an alternative solution. @AlexFarber: Genering IL code for putting all arguments into an array seems to be very complicated... I wonder if there is a simpler approach. – Sergiy Belozorov Sep 09 '13 at 17:55
  • @SergiyByelozyorov There is not. – Servy Sep 12 '13 at 14:32
  • What or who, exactly, determines the return type and signature of the callback? – grieve Sep 12 '13 at 18:42
  • @grieve: I have a delegate representing a method that receives callbacks as parameters and need to generate fake callback that will map onto a generic function (`AnyFunc`): http://tny.cz/35c69df5. – Sergiy Belozorov Sep 12 '13 at 19:30
  • I'm working on a reflection solution for this. Stand by. – Adam Maras Sep 12 '13 at 20:33
  • @SergiyByelozyorov: So in this example is the `notifyAboutNewObjects` method what your delegate represents? – grieve Sep 12 '13 at 21:23

3 Answers3

6

I wrote a opensource PCL library, called Dynamitey (in nuget), that does all sorts of dynamic things using the C# DLR. .

It specifically has a static method called Dynamic.CoerceToDelegate(object invokeableObject, Type delegateType) That basically wraps the dynamic invocation of a DynamicObject or a more general delegate, with the specific Type of delegate using CompiledExpressions (source).

using System.Dynamic you can create an invokable object:

public class AnyInvokeObject:DynamicObject{
    Func<object[],object> _func;
    public AnyInvokeObject(Func<object[],object> func){
       _func = func;
    }
    public override bool TryInvoke(InvokeBinder binder, object[] args, out object result){
       result = _func(args);
       return true;
    }
}

Then in your sample:

var tmpObj = new AnyInvokeObject(args => {
                     Console.WriteLine("Invoked dynamic delegate with following parameters:");
                     for (var j = 0; j < args.Length; j++)
                         Console.WriteLine("  {0}: {1}", j, args[j]);
                     if (retType != typeof(void))
                         return Activator.CreateInstance(retType);
                     return null;
                });
methodParams[i] = Dynamic.CoerceToDelegate(tmpObj, paramInfo.ParameterType);
jbtule
  • 31,383
  • 12
  • 95
  • 128
  • That looks very promising, but I couldn't understand it all. Too much C# magic for me :-). But I will have another look tomorrow and try to use it in my project. – Sergiy Belozorov Sep 12 '13 at 17:13
  • Wow! Dynamitey is an impressive piece of work. I've used it's predecessor ImpromptuInterface because I need .NET 4.0. However, I still can't make my code work. Here is the snippet: http://tny.cz/40a1846e. Impromptu creates correct delegate types, but I receive `TargetInvocationException` when created delegates are invoked. Full details of the exception: http://tny.cz/12afea65. – Sergiy Belozorov Sep 12 '13 at 19:15
  • updated with some code. Also the nuget package contains a separate .net 4.0 version of Dynamitey too. I'd recommend it, as I'm obsoleting impromptu's version of these methods in the next version. – jbtule Sep 13 '13 at 00:59
  • 2
    Hat is off to you, this is beautiful, I wrote something not quite as elegant for one of my projects, I could be rewriting it now I've seen this.. – BaconSah Sep 13 '13 at 01:38
  • Thank you so much. It works great! Is there perhaps any shorter way of writing this or must I really create a class derived from `DynamicObject`? As for the Dynamitey for 4.0 - I would be glad to use it, but unfortunately here in MonoDevelop in Ubuntu I don't have the greatness of nuGet. How can I get it otherwise? – Sergiy Belozorov Sep 13 '13 at 10:05
  • Nuget is much better at working on ubuntu these days, however, it's still not seamless. I uploaded the binaries to https://github.com/ekonbenefits/dynamitey/releases/tag/1.0.2 – jbtule Sep 13 '13 at 12:48
  • 2
    @SergiyByelozyorov Oh also, I did the dynamic object because I thought there was a dlr issue with `params` keyword. That was an incorrect assumption, it was a permission issue, just change your `AnyFunc` delegate to `public` and your original code should work too. – jbtule Sep 13 '13 at 13:17
  • Wow! I didn't know they have nuGet for MonoDevelop too. This is great! – Sergiy Belozorov Sep 13 '13 at 22:39
0

You could either check out the source code for SignalR or simply just use it.

Register a function in browser that the server can call

var connection = $.hubConnection();
var yourHubProxy = connection.createHubProxy('yourHub');
yourHubProxy.on('addMessageToConsole', function (message) {
  console.log(message);
});

and on the server

public class YourHub : Hub
{
    public void SendMessage(string message)
    {
        Clients.All.addMessageToConsole(message);
    }
}

See here for more examples.

Jonas Elfström
  • 30,834
  • 6
  • 70
  • 106
  • Interesting system, but unfortunately it doesn't have all the properties that we require from the middleware. In fact, we believe, there is no system out there that does. That's why we create our own. – Sergiy Belozorov Sep 12 '13 at 15:19
-1

How about a solution without delegates? (warning: rampant pseudocode)

class AbstractServerToClientMessage {
     public virtual string ToJSON();
}
class OnNewObjectMessage: AbstractServerToClientMessage {...}
class OnSomethingElseHappenedMessage: AbstractServerToClientMessage {...}

void NotifyClient(AbstractServerToClientMessage message)

event OnNewObject;
event OnSomethingElseHappened;

void notifyAboutNewObjects() {
    ...
    OnNewObject += NotifyClient;
    ...
}

void AddNewObject(SomeObject obj) {
    OnNewObjectMessage message(obj);
    OnNewObject(message);
    // actually add the object
}

messages will be serialized anyway, so why bother? Polymorphism will take care of the rest. The only requirement is to have a set of messages which correspond to each event type.

Returning the value may be implemented by writing to some field in the AbstractServerToClientMessage.


Or, you can actually have a delegate with a fixed signature accepting a similar AbstractServerToClientMessage. Again, polymorphism (+ a class factory for deserialization) will allow to cast it to correct type of message.

DarkWanderer
  • 8,739
  • 1
  • 25
  • 56
  • 1
    Thanks for the idea. But I do need arbitrary delegates. Currently I am using `delegate object FuncWrapper(param object[] args)` and users write `void notifyOnSmth(FuncWrapper callback)`. However I want to provide better interface to users, so that they can write arbitrary delegates, e.g. `void notifyOnSmth(Action callback)`. This will give them better idea of what parameters does callback accept and will enable compiler-time validation of the calls to `callback`. – Sergiy Belozorov Sep 12 '13 at 15:06
  • Also having "a set of messages" is not possible, because middleware must be generic to support any application, thus any message. – Sergiy Belozorov Sep 12 '13 at 15:15