Мне не хватает чего-то простого, но я не могу понять это. Данный:

abstract class EventBase {}
class SpecialEvent : EventBase {}

В другом классе я хочу, чтобы вызывающие абоненты могли RegisterFor<SpecialEvent>(x => {...})

public class FooHandler {
{
    internal Dictionary<Type, Action<EventBase>> _validTypes = new Dictionary<Type, Action<EventBase>>();

    internal void RegisterFor<T>(Action<T> handlerFcn) where T: EventBase
    {
        _validTypes.Add(typeof(T), handlerFcn);
    }
 }

Однако строка _validTypes.Add не компилируется. Он не может преобразовать Action<T> в Action<EventBase>. Ограничение указывает, что T должен быть получен из EventBase, так что же я неправильно понимаю?

7
AlG 24 Фев 2018 в 02:28

5 ответов

Лучший ответ

C # правильно запретить это. Чтобы понять почему, рассмотрим эту ситуацию:

// This is your delegate implementation
void SpecialAction(SpecialEvent e) {
    Console.WriteLine("I'm so special!");
}

// This is another EventBase class
class NotSoSpecialEvent : EventBase {}

void PureEvil(Action<EventBase> handlerFcn) where T: EventBase {
    handlerFcn(new NotSoSpecialEvent()); // Why not?
}

Давайте представим, что C # позволяет вам передать Action<SpecialEvent> для Action<EventBase>. Вот что будет дальше:

PureEvil(SpecialAction); // Not allowed

Теперь PureEvil попытается передать NotSoSpecialEvent делегату SpecialAction, который требует SpecialEvent, что никогда не должно происходить.

6
dasblinkenlight 23 Фев 2018 в 23:38

Делегаты действий являются контравариантными - тип определяется как Action<in T>. Это влияет на принцип замещения применительно к действиям.

Рассмотрим два типа: B (базовый) и D (производный). Тогда Action<B> более производный , чем Action<D>. Это подразумевает следующее поведение:

class B { }
class D : B { }
...
Action<D> derivedAction = ...
Action<B> baseAction = derivedAction;  // Fails
Action<D> derivedAction1 = baseAction; // Succeeds

В вашем примере, когда SpecialEvent является типом, производным от EventBase, вам разрешено присваивать только Action<SpecialEvent> = Action<EventBase>, но не наоборот (как вы пытаетесь).

Поскольку вы уже проверяете тип события перед сохранением делегатов в словаре, вам не нужно настаивать на хранении строго типизированных делегатов - не говоря уже о том, что вы не можете настаивать на строгой типизации из-за контравариантности Action делегатов.

Вы можете хранить в словаре все, что захотите, например, Delegate или простой object, а затем опускается до конкретного Action<T>, когда приходит время получить делегата Action из коллекции.

public class FooHandler
{
    internal Dictionary<Type, object> _validTypes = 
        new Dictionary<Type, object>();

    internal void RegisterFor<T>(Action<T> handlerFcn) 
        where T: EventBase
    {
        _validTypes.Add(typeof(T), handlerFcn);
    }

    internal Action<T> Find<T>()
        where T : EventBase =>
        (Action<T>)_validTypes[typeof(T)];
 }
4
Zoran Horvat 24 Фев 2018 в 00:02

Action<SpecialEvent> нельзя использовать как Action<EventBase>.

Используйте Delegate для абстрагирования параметров, а затем приведите их обратно:

public class FooHandler
{
    internal Dictionary<Type, Delegate> _validTypes = new Dictionary<Type, Delegate>();

    internal void RegisterFor<T>(Action<T> handlerFcn) where T : EventBase
    {
        _validTypes.Add(typeof(T), handlerFcn);
    }

    internal void ExecuteHandler<T>(T value) where T : EventBase
    {
        var handler = (Action<T>)_validTypes[typeof(T)];
        handler(value);
    }
}

Используйте это так:

var handler = new Action<SpecialEvent>(ev => { Console.WriteLine("works!"); });
FooHandler.RegisterFor<SpecialEvent>(handler);
FooHandler.ExecuteHandler(new SpecialEvent());
2
Ivan Sanz-Carasa 23 Фев 2018 в 23:56

Что если некоторые Action<EventBase> в Dictionary являются Action<UnexpectedEvent> вместо Action<SpecialEvent>? Action<T> является контравариантным, но не ковариантным.

См. https : //blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

Если вы знаете , что типы должны сработать, и / или хотите получить исключение, если типы конфликтуют, вы можете заключить его в другое действие, которое выполняет приведение, например так:

_validTypes.Add(typeof(T), eventBase => handlerFcn((T)eventBase));
0
Dave Cousineau 23 Фев 2018 в 23:53

У вас есть Action<SpecialEvent>, который, как известно, обрабатывает только SpecialEvent. Action<EventBase> означает, что любой EventBase может быть передан ему. Это делает преобразование небезопасным.

В вашем случае я бы выбрал вместо Dictionary<Type, Delegate>, где каждый ключ T связан со значением Action<T>. Если вы можете гарантировать, что добавляете только значения правильного типа, вы можете безопасно привести делегат обратно к Action<T>, когда захотите его вызвать.

2
user743382user743382 23 Фев 2018 в 23:35