开发者

add generic Action<T> delegates to a list

开发者 https://www.devze.com 2023-01-09 02:43 出处:网络
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.

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<SomeCl开发者_运维问答ass<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


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


Will this do what you want?

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


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


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


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


I don't know if this is what you want exactly but if you want to have a method which invoke an action for each element in a list you can use an extension method like that :

 public static class Extensions
    {
        public static void Action<T>(this IEnumerable<T> list, Action<T> action)
        {
            foreach (T element in list) 
            {
                action.Invoke(element);
            }
        }
    }

An exemple of call with myList of type IEnumerable<string>:

 myList.Action(element => Console.WriteLine(element));

Maybe LINQ already implements an action in a List but if it's the case I don't know the syntax.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号