add generic Action<T> delegates to a list

19,247

Solution 1

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

Solution 2

Will this do what you want?

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

Solution 3

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

Solution 4

using System;
using System.Collections.Generic;

public delegate void MyDelegate<T>( T i );

public class DelegateList<T>
{
    public void Add( MyDelegate<T> del ) {
        imp.Add( del );
    }

    public void CallDelegates( T k ) {
        foreach( MyDelegate<T> del in imp ) {
            del( k );
        }
    }

    private List<MyDelegate<T> > imp = new List<MyDelegate<T> >();
}

public class MainClass
{
    static void Main() {
        DelegateList<int> delegates = new DelegateList<int>();

        delegates.Add( PrintInt );
        delegates.CallDelegates( 42 );
    }

    static void PrintInt( int i ) {
        Console.WriteLine( i );
    }
}
Share:
19,247
jochen
Author by

jochen

Updated on July 21, 2022

Comments

  • jochen
    jochen almost 2 years

    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