добавьте универсальное Действие <T> делегаты в списке

Действительно ли возможно добавить универсальное Действие делегата к набору Списка? Мне нужна некоторая простая система обмена сообщениями для приложения 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

12
задан jochen 23 July 2010 в 16:12
поделиться

4 ответа

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.

17
ответ дан 2 December 2019 в 07:02
поделиться

Будет ли это делать то, что вы хотите?

void Add<T>(Action<SomeClass<T>> foo)
    where T : SomeClassBase
{
    _actions.Add(x => foo((SomeClass<T>) x));
}
3
ответ дан 2 December 2019 в 07:02
поделиться

Не уверен, что это то, что вам нужно. Но попробуйте изменить метод 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);
1
ответ дан 2 December 2019 в 07:02
поделиться

Если вы посмотрите на строку

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

Класс T, на который вы ссылаетесь, нигде не объявлен. В SomeClass у вас есть правильное объявление для универсального класса, но в вашем классе App вы не сказали ему, что такое T в этом конкретном экземпляре.

Таким образом, я не думаю, что это делает то, что вы хотите. С дженериками проще всего представить, что когда код скомпилирован, не существует такой вещи, как дженерики [0]. Что во время компиляции он просто создает все классы, которые вы используете в целом. Это означает, что на самом деле нет концепции списка общих классов, поскольку к тому времени, когда вы их используете, классы имеют данный тип и поэтому не могут быть смешаны.

Я думаю, что для этого нужно использовать более определенные определения классов, но, как объяснил Джон Скит, это тоже не работает.

Возможно, лучше всего сделать несколько шагов назад и задать вопрос о том, что вы делаете с этой системой обмена сообщениями?

[0] Дженерики работают по-разному на разных языках, но я думаю, это хороший примерный принцип для работы. ..

0
ответ дан 2 December 2019 в 07:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: