Добавить общий список участников мероприятия <T> в список
Можно ли добавить общий делегат Action к коллекции List?
Мне нужна какая-то простая система обмена сообщениями для приложения Silverlight.
UPDATE
Следующее - это то, что я действительно "хочу"
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 );
}
}
Компилятор:
The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)
начальный код отключен класс 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 );
}
}
Компилятор жалуется - для строки _actions.Add();
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
Со стороны приложения нет необходимости в классе SomeClassBase, но представляется невозможным определить список элементов Action<SomeClass<T>>
, и подход с базовым классом работает при использовании класса в списке вместо Action
Спасибо, Jochen
Ответы
Ответ 1
EDIT: Хорошо, теперь я вижу, что вы пытаетесь сделать. Я оставил старый ответ ниже для потомков:)
К сожалению, вы не можете выразить отношения, которые вы хотите использовать в генераторах С#, но поскольку вы можете убедиться, что вы единственный, кто управляет коллекцией, вы можете сохранить ее в безопасности:
Попробуйте следующее:
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);
}
}
}
}
Обратите внимание, что вы можете использовать тот факт, что делегаты являются многоадресными, чтобы просто сохранить Dictionary<Type, Delegate>
и объединить их вместе, но я оставлю это как упражнение для читателя:)
Старый ответ
Это не по уважительной причине. Позвольте избавиться от дженериков (поскольку они здесь неактуальны) и подумайте о более простом случае - фруктах и бананах.
Вы пытаетесь добавить Action<Banana>
в List<Action<Fruit>>
. Вы не можете этого сделать - даже с общей дисперсией С# 4. Почему? Потому что это не безопасно. Рассмотрим это:
Action<Banana> peeler = banana => banana.Peel();
List<Action<Fruit>> fruitActions = new List<Action<Fruit>>();
fruitActions.Add(peeler); // Nope!
fruitActions[0].Invoke(new Strawberry());
Ик! Теперь у нас есть банановый чистильщик, пытающийся очистить клубнику... какой беспорядок!
Нельзя сказать, что в С# 4 было бы наоборот:
Action<Fruit> eater = fruit => fruit.Eat();
List<Action<Banana>> bananaActions = new List<Action<Banana>>();
fruitActions.Add(eater); // Yes!
fruitActions[0].Invoke(new Banana());
Здесь мы добавляем Action<Fruit>
в List<Action<Banana>>
- это приемлемо, потому что все, что вы можете сделать для Action<Banana>
, также справедливо для Action<Fruit>
.
Ответ 2
Будет ли это делать то, что вы хотите?
void Add<T>(Action<SomeClass<T>> foo)
where T : SomeClassBase
{
_actions.Add(x => foo((SomeClass<T>) x));
}
Ответ 3
Не уверен, что это то, что вы хотите. Но попробуйте изменить метод добавления:
void Add( Action<SomeClassBase> foo )
{
_actions.Add( foo );
}
Обновление
Это позволит вам сделать что-то вроде этого:
App app = new App();
Action<SomeClass<int>> action = null; // Initilize it...
app.Add((Action<SomeClassBase>)action);
Ответ 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 );
}
}
Ответ 5
Если вы посмотрите на строку
List<Action<SomeClass<T>>> _actions = new List<Action<SomeClass<T>>>();
Класс T, о котором вы говорите, не был объявлен нигде. В SomeClass у вас есть правильное объявление для универсального класса, но в вашем классе App вы не сказали ему, что такое T в этом конкретном экземпляре.
В целом я не думаю, что это делает то, что вы хотите. С дженериками легче всего представить, что при компиляции кода нет такой вещи, как generics [0]. Во время компиляции он просто делает все классы, которые вы используете в общем. Это означает, что на самом деле нет понятия списка общих классов, поскольку к тому времени, когда вы их используете, классы имеют заданный тип и поэтому не могут быть смешаны.
Я думаю, что он должен работать, используя более определенные определения классов, но, как пояснил Джон Скит, это тоже не работает.
Возможно, лучшая идея - сделать несколько шагов назад и задать вопрос о том, что вы делаете с этой системой обмена сообщениями?
[0] Дженерики работают по-разному на разных языках, но это хороший грубый принцип работы, я думаю...
Ответ 6
Я не знаю, точно ли это то, что вы хотите, но если вы хотите иметь метод, вызывающий действие для каждого элемента в списке, вы можете использовать такой метод расширения:
public static class Extensions
{
public static void Action<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (T element in list)
{
action.Invoke(element);
}
}
}
Пример вызова с myList типа IEnumerable<string>
:
myList.Action(element => Console.WriteLine(element));
Возможно, LINQ уже реализует действие в списке, но если это случай, я не знаю синтаксиса.