views:

144

answers:

4

Is it possible to add a generic delegate Action to a List collection? I need some kind of simple messaging system for a Silverlight application.

UPDATE The following is what i realy "want"

class SomeClass<T>
{
    public T Data { get; set; }
    // and more ....
}

class App
{
    List<Action<SomeClass<T>>> _actions = new List<Action<SomeClass<T>>>();

    void Add<T>( Action<SomeClass<T>> foo )
    {
        _actions.Add( foo );
    }
}

Compiler:

The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)

initial code snipped class SomeClassBase { }

class SomeClass<T> : SomeClassBase
{
    public T Data { get; set; }
    // and more ....
}

class App
{
    List<Action<SomeClassBase>> _actions = new List<Action<SomeClassBase>>();

    void Add<T>( Action<SomeClass<T>> foo )
        where T : SomeClassBase
    {
        _actions.Add( foo );
    }
}

The compiler complains - for the _actions.Add() line;

Argument 1: cannot convert from 'System.Action<test.SomeClass<T>>' to 'System.Action<test.SomeClassBase>'
The best overloaded method match for 'System.Collections.Generic.List<System.Action<test.SomeClassBase>>.Add(System.Action<test.SomeClassBase>)' has some invalid arguments

From the application side there is no need for the SomeClassBase class, yet it seems impossible to define a List of Action<SomeClass<T>> elements and the approach with the base-class works when using the class in the List, instead of the Action

Thanks, jochen

+1  A: 

Not sure if this is what you want. But try to change you Add method to:

void Add( Action<SomeClassBase> foo )
{
   _actions.Add( foo );
}

Update

This will allow you to do something like this:

App app = new App();

Action<SomeClass<int>> action = null; // Initilize it...

app.Add((Action<SomeClassBase>)action);
Martin Ingvar Kofoed Jensen
I see. that means a cast when passing the parameter to the add. this might be helpful - thanks
jochen
A: 

Will this do what you want?

void Add<T>(Action<SomeClass<T>> foo)
    where T : SomeClassBase
{
    _actions.Add(x => foo((SomeClass<T>) x));
}
Brian Genisio
Thanks Brian,this does what i want - yet i havent fully figured out why ;-)
jochen
It works because you are creating a lightweight anonymous function that defers to the foo parameter, but casts the argument as the type that the compiler wants.
Brian Genisio
+3  A: 

EDIT: Okay, now I see what you're trying to do. I've left the old answer below for posterity :)

Unfortunately you can't express the relationship you want in C# generics, but as you can make sure you're the only one manipulating the collection, you can keep it safe yourself:

Try this:

class App
{
     private readonly Dictionary<Type, object> delegateMap;

     void Add<T>(Action<SomeClass<T>> foo)
     {
         object tmp;
         if (!delegateMap.TryGetValue(typeof(T), out tmp))
         {
              tmp = new List<Action<SomeClass<T>>>();
              delegateMap[typeof(t)] = tmp;
         }
         List<Action<SomeClass<T>> list = (List<Action<SomeClass<T>>) tmp;
         list.Add(foo);
     }

     void InvokeActions<T>(SomeClass<T> item)
     {
         object tmp;
         if (delegateMap.TryGetValue(typeof(T), out tmp))
         {
             List<Action<SomeClass<T>> list = (List<Action<SomeClass<T>>) tmp;
             foreach (var action in list)
             {
                 action(item);
             }
         }
     }
}

Note that you could use the fact that delegates are multicast to just keep a Dictionary<Type, Delegate> and combine them together, but I'll leave that as an exercise for the reader :)


Old answer

It's failing for a good reason. Let's get rid of the generics (as they're irrelevant here) and think about a simpler case - fruit and bananas.

You're trying to add an Action<Banana> to a List<Action<Fruit>>. You can't do that - even with the generic variance of C# 4. Why? Because it's not safe. Consider this:

Action<Banana> peeler = banana => banana.Peel();
List<Action<Fruit>> fruitActions = new List<Action<Fruit>>();
fruitActions.Add(peeler); // Nope!
fruitActions[0].Invoke(new Strawberry());

Eek! Now we've got a banana peeler trying to peel a strawberry... what a mess!

Not that the other way round would be acceptable in C# 4:

Action<Fruit> eater = fruit => fruit.Eat();
List<Action<Banana>> bananaActions = new List<Action<Banana>>();
fruitActions.Add(eater); // Yes!
fruitActions[0].Invoke(new Banana());

Here we're adding an Action<Fruit> to a List<Action<Banana>> - that's acceptable, because anything you can do to an Action<Banana> is also valid for an Action<Fruit>.

Jon Skeet
these juciy bits make sense. But actually I dont 'want'/need the baseclass at all. maybe i should have made my intentions more specific.what i need is a list of actions which all receive one argument of the same type SomeClass<T>. If the type is different for another action there will be a new List.it should finally be a dictonary mapping form <pre>Type to List<Action<Type>><pre>
jochen
@jochen: Okay, I'm with you. Editing.
Jon Skeet
You may need to go dynamic then. Use a `Dictionary<Type, List<Delegate>>`, and `Delegate.DynamicInvoke` to invoke the delegates.
thecoop
@Jon: Thanks!your latest code is exactly how I want it to work. I will try to figure out the multicast solution as well.
jochen
one small typo tmp = new List<Delegate>();should rather be tmp = new List<Action<SomeClass<T>>(); else we get InvalidCast exceptions
jochen
@jochen: Oops, fixed.
Jon Skeet
A: 

If you look at the line

List<Action<SomeClass<T>>> _actions = new List<Action<SomeClass<T>>>();

The class T that you are referring to hasn't been declared anywhere. In SomeClass you have the right declaration for a generic class but in your App class you haven't told it what T is in this particular instance.

In summary I don't think this is doing what you want it to. With generics its easiest to imagine that when the code has been compiled there is no such thing as generics[0]. That during the compilation its just making all the classes you are using generically. This means there isn't really a concept of a list of generic classes since by the time you are using them the classes are of a given type and so can't be mixed.

I think the way it would need to work is using more definite class definitions but as Jon Skeet explained that doesn't really work either.

Perhaps the best idea is to take a few step backs and ask a question about what you are doing with this messaging system?

[0] Generics work differently in different languages but this is a good rough principle to work on I think...

Chris
@Chris, Thanksgood point - taking the step back.... I just started working with generics and thought it would be interesting to use them. But for my specific example it is really not required.
jochen