Разница между += и Delegate.Combine

Я построил систему событий, которая поддерживает dictionary делегатов, добавляет/удаляет элементы к этим dictionary с помощью общих методов подписки/отписки (каждый из которых выполняет действие type T) и имеет метод публикации для уведомления подписчиков, когда что-то происходит. (который также принимает действие типа T). Все работает нормально, но я заметил, что не могу использовать += или -= при добавлении или удалении элементов в мой dictionary, так как типы, переданные в методы (Action of T), не соответствуют типам, хранящимся в dictionary (Delegate ). Следующий фрагмент показывает, что я могу и не могу делать.

private readonly Dictionary<Type, Delegate> delegates = new Dictionary<Type, Delegate>();

public void Subscribe<T>(Action<T> del)
{
    if (delegates.ContainsKey(typeof(T)))
    {
        // This doesn't work!
        delegates[typeof(T)] += del as Delegate;
        // This doesn't work!
        delegates[typeof(T)] += del;
        // This is ok
        delegates[typeof(T)] = (Action<T>)delegates[typeof(T)] + del;
        // This is ok
        var newDel = (Action<T>)delegates[typeof(T)] + del;
        delegates[typeof(T)] = newDel;
        // This is ok
        del += (Action<T>)delegates[typeof(T)];
        delegates[typeof(T)] = del;
        // This is ok
        delegates[typeof(T)] = Delegate.Combine(delegates[typeof(T)], del);
    }
    else
    {
        delegates[typeof(T)] = del;
    }
}

Я в основном понимаю ответ Джона Скита здесь += operator for Delegate, в частности, эта часть

Двоичный оператор + выполняет комбинацию делегатов, когда оба операнда относятся к некоторому типу делегата D. (Если операнды имеют разные типы делегата, возникает ошибка времени связывания.)

Чего я не понимаю, так это

Компилятор превращает его в вызов Delegate.Combine. Обратная операция с использованием - или -= использует Delegate.Remove.

Что именно происходит, когда я использую += или -= вместо Delegate.Combine? Каковы различия между ними, делающие одну реализацию действительной, а другую недействительной?


person Chris Riley    schedule 11.04.2019    source источник
comment
Это должно быть ясно - компилятор должен знать, с чем он работает, чтобы выполнить Delegate.Combine. У него недостаточно информации, основанной на объявленном типе delegates, чтобы принять это решение.   -  person Damien_The_Unbeliever    schedule 11.04.2019
comment
Все работающие (кроме последнего) работают, потому что вы вставляете приведения. Предоставление компилятору больше информации, потому что вы лучше, чем он, знаете, что такое фактический тип.   -  person Damien_The_Unbeliever    schedule 11.04.2019


Ответы (1)


В конце ответа, который вы связали, говорится:

Поскольку System.Delegate не является типом делегата, оператор + для него не определен.

Это объясняет, почему это не работает (оба операнда Delegate):

delegates[typeof(T)] += del as Delegate;

Delegate.Combine работает, потому что он объявлен как это:

public static Delegate Combine (params Delegate[] delegates);

Обратите внимание, как он принимает параметры типа Delegate вместо определенного типа делегата. И он выдаст ArgumentException, если делегаты не одного типа.

Таким образом, компилятор не только изменяет оператор + на Delegate.Combine, но и выполняет некоторую проверку типов! С другой стороны, проверка типов во время компиляции не выполняется, если вы используете Delegate.Combine напрямую. Delegate.Combine проверяет типы только во время выполнения.

Все остальные строки работают, потому что вы выполняете кастинг, т.е. сообщаете компилятору, к какому типу относятся делегаты, делая оба операнда + типа Action<T>.

person Sweeper    schedule 11.04.2019
comment
Спасибо за это, я думаю, что теперь я лучше понимаю. Я немного отредактировал сообщение, включив в него дополнительную строку, которая не работает, поскольку приведение к делегату было неправильным/вводящим в заблуждение относительно моего замешательства. Думаю, теперь я лучше понимаю, что здесь происходит, особенно потому, что System.Delegate не является типом делегата, для него не определен оператор + Спасибо! - person Chris Riley; 11.04.2019
comment
Обратите внимание, что ко- и контравариантность универсальных типов делегатов здесь частично нарушили безопасность типов. Например, Action<in T> является контравариантным (in) в T, и мы можем создать следующий пример: Action<ICloneable> act1 = x => x.Clone(); Action<IConvertible> act2 = x => x.GetTypeCode(); Action<string> actStr1 = act1; /* OK, contravariance */ Action<string> actStr2 = act2; /* OK, contravariance */ actStr1("dummy"); /* works */ actStr2("dummy"); /* works */ actStr1 += actStr2; /* does not complain at binding-time, blows up at run-time */ - person Jeppe Stig Nielsen; 11.04.2019