Действительно ли возможно добавить универсальное Действие делегата к набору Списка? Мне нужна некоторая простая система обмена сообщениями для приложения Silverlight.
ОБНОВЛЕНИЕ следующее - то, что я действительно "хочу"
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. Добавьте () строку;
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>>
элементы и подход с базовым классом работают при использовании класса в Списке вместо Действия
Спасибо, jochen
EDIT: Хорошо, теперь я понимаю, что вы пытаетесь сделать. Я оставил старый ответ ниже для потомков :)
К сожалению, вы не можете выразить нужные вам отношения в C# generics, но поскольку вы можете убедиться, что вы единственный, кто манипулирует коллекцией, вы можете сохранить ее в безопасности сами:
Попробуйте это:
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
и объединить их вместе, но я оставлю это как упражнение для читателя :)
Старый ответ
Это не работает по хорошей причине. Давайте избавимся от дженериков (поскольку они здесь неуместны) и подумаем о более простом случае - фруктах и бананах.
Вы пытаетесь добавить Action
в List
. Вы не сможете этого сделать - даже с помощью общей дисперсии C# 4. Почему? Потому что это небезопасно. Рассмотрим следующее:
Action<Banana> peeler = banana => banana.Peel();
List<Action<Fruit>> fruitActions = new List<Action<Fruit>>();
fruitActions.Add(peeler); // Nope!
fruitActions[0].Invoke(new Strawberry());
Ик! Теперь мы имеем банановую кожуру, пытающуюся очистить клубнику... какая неприятность!
Не то чтобы другой способ был приемлем в 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());
Здесь мы добавляем Action
к List
- это приемлемо, потому что все, что можно сделать с Action
, справедливо и для Action
.
Будет ли это делать то, что вы хотите?
void Add<T>(Action<SomeClass<T>> foo)
where T : SomeClassBase
{
_actions.Add(x => foo((SomeClass<T>) x));
}
Не уверен, что это то, что вам нужно. Но попробуйте изменить метод Add на:
void Add( Action<SomeClassBase> foo )
{
_actions.Add( foo );
}
Update
Это позволит вам сделать что-то вроде этого:
App app = new App();
Action<SomeClass<int>> action = null; // Initilize it...
app.Add((Action<SomeClassBase>)action);
Если вы посмотрите на строку
List<Action<SomeClass<T>>> _actions = new List<Action<SomeClass<T>>>();
Класс T, на который вы ссылаетесь, нигде не объявлен. В SomeClass у вас есть правильное объявление для универсального класса, но в вашем классе App вы не сказали ему, что такое T в этом конкретном экземпляре.
Таким образом, я не думаю, что это делает то, что вы хотите. С дженериками проще всего представить, что когда код скомпилирован, не существует такой вещи, как дженерики [0]. Что во время компиляции он просто создает все классы, которые вы используете в целом. Это означает, что на самом деле нет концепции списка общих классов, поскольку к тому времени, когда вы их используете, классы имеют данный тип и поэтому не могут быть смешаны.
Я думаю, что для этого нужно использовать более определенные определения классов, но, как объяснил Джон Скит, это тоже не работает.
Возможно, лучше всего сделать несколько шагов назад и задать вопрос о том, что вы делаете с этой системой обмена сообщениями?
[0] Дженерики работают по-разному на разных языках, но я думаю, это хороший примерный принцип для работы. ..