Henrik Karlsson
Henrik Karlsson

Reputation: 5723

invoke Action with unknown type parameter (Action<any>)

I am creating an API, and my goal is to expose a method that can be invoked like this:

Library.AddCallback<string>(Type.ChatMessage, GotMessage);

private void GotMessage(string message) {
    //...
}
//or
Library.AddCallback<int>(Type.Number, GotNumber);

private void GotNumber(int number) {
    //...
}

The type <int>, <string> can be any type.

In the library, the method looks something like this:

public void AddCallback<T1>(object type, Action<T1> callback) {
    //...
}

The problem is that I somehow want to first save the callback outside of the method call (in a list) and then later be able to call it.

What I ideally want to do is to first cast it to an object to be able to store it in a List<object> and then cast it back to Action<T1>. However it seems like it is impossible to save T1 to a variable (except by doing typeof(T1) which denies me to use it to cast it back).

Example of how I would like to call it (where I got type and data from a list):

((Action<type>)callback)(data)

Upvotes: 5

Views: 2087

Answers (2)

Jon
Jon

Reputation: 437414

You can do something like this:

List<object> callbacks = new List<object>();

public void AddCallback<T1>(object type, Action<T1> callback)
{
    this.callbacks.Add(callback);
}

public IEnumerable<Action<T>> GetCallbacks<T>()
{
    return this.callbacks.OfType<Action<T>>();
}

And use it like this:

// Sample callbacks
static void Foo(int i) { Console.WriteLine("Foo {0}", i); }
static void Bar(string s) { Console.WriteLine("Bar {0}", s); }

AddCallback(null, (Action<int>)(Foo));
AddCallback(null, (Action<string>)(Bar));

foreach (var callback in GetCallbacks<int>())
{
    callback(42); // only calls Foo
}

Upvotes: 2

Chris Sinclair
Chris Sinclair

Reputation: 23208

I'm not sure how data is typed. Assuming it's type object for now, you can decompose the Action<T> to Action<object> and perform a cast in them:

private List<Action<object>> Callbacks = new List<Action<object>>();

public void AddCallback<T1>(object type, Action<T1> callback) 
{
    Callbacks.Add((data) => callback((T1)data));
}

public void FireCallback(object data)
{
    Action<object> callback = GetCallback();
    callback(data);
}

EDIT: You've already marked it as an answer, but here's another implementation that stores the callbacks in a typed set.

A CallbackHandler stores the typed list of callbacks:

public class CallbackHandler<T> : ICallbackHandler
{
    private List<Action<T>> Callbacks = new List<Action<T>>();

    public void AddCallback<T>(Action<T> callback)
    {
        Callbacks.Add(callback);
    }

    public void Callback(object data)
    {
        T typedData = (T)data;
        foreach(var callback in Callbacks)
            callback(typedData);
    }
}

public interface ICallbackHandler
{
    void Callback(object data);
}

Then your higher level Library has something like this:

private Dictionary<Type, ICallbackHandler> AllCallbacks = new Dictionary<Type, ICallbackHandler>();

public void AddCallback<T>(Action<T> callback) 
{
    Type type = typeof(T);
    ICallbackHandler handler;
    if (!AllCallbacks.TryGetValue(type, out handler))
    {
        handler = new CallbackHandler<T>();
        AllCallbacks[type] = handler;
    }
    CallbackHandler<T> typedHandler = (CallbackHandler<T>)handler;
    typedHandler.AddCallback(callback);
}

public void FireCallback(object data)
{
    Type type = data.GetType();
    ICallbackHandler handler;
    AllCallbacks.TryGetValue(type, out handler);
    if (handler != null)
        handler.Callback(data);
}

This is assuming that the type of data determines which callback(s) to fire. If you need to put one more level on it (based on Type.ChatMessage or Type.Number) it shouldn't be too difficult.

Upvotes: 6

Related Questions