Почему TEventArgs не стал противоречивым в стандартном шаблоне событий в экосистеме .NET?

Узнав больше о стандартной модели событий в .NET, я обнаружил, что перед введением обобщений в С# метод, который будет обрабатывать событие, представлен этим типом делегата:

//
// Summary:
//     Represents the method that will handle an event that has no event data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains no event data.
public delegate void EventHandler(object sender, EventArgs e);

Но после того, как дженерики были введены в С# 2, я думаю, что этот тип делегата был переписан с использованием дженериков:

//
// Summary:
//     Represents the method that will handle an event when the event provides data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains the event data.
//
// Type parameters:
//   TEventArgs:
//     The type of the event data generated by the event.
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

У меня есть два вопроса здесь:

Во-первых, почему параметр типа TEventArgs не был сделан контравариантным?

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

В книге Джозефа Албахари, С# в двух словах, я цитирую:

Если вы определяете универсальный тип делегата, рекомендуется:

  • Отметьте параметр типа, используемый только для возвращаемого значения, как ковариантный (out).
  • Отметьте любые типы параметров, используемые только для параметров, как контравариантные (в).

Это позволяет преобразованиям работать естественным образом, уважая отношения наследования между типами.

Второй вопрос: почему не было общего ограничения для обеспечения того, что TEventArgs наследуется от System.EventArgs?

Следующее:

public delegate void EventHandler<TEventArgs>  (object source, TEventArgs e) where TEventArgs : EventArgs; 

Заранее спасибо.

Отредактировано, чтобы уточнить второй вопрос:

Похоже, что общее ограничение для TEventArgs (где TEventArgs: EventArgs) существовало ранее и было удалено Microsoft, так что команда разработчиков, похоже, поняла, что это не имеет большого практического смысла.

Я отредактировал свой ответ, включив в него некоторые скриншоты из

Справочный источник .NET

enter image description here

Ответ 1

Прежде всего, чтобы ответить на некоторые проблемы в комментариях к вопросу: я, как правило, настойчиво отвечаю на вопросы "почему нет", потому что трудно найти краткие причины, почему все в мире решили не делать эту работу, и потому что вся работа не сделано по умолчанию. Скорее, вам нужно найти причину для выполнения работы и отнять ресурсы у другой работы, которая менее важна для ее выполнения.

Более того, вопросы "почему нет" этой формы, в которых задаются вопросы о мотивации и выборе людей, работающих в конкретной компании, могут быть подотчетны только тем людям, которые приняли это решение, которых, вероятно, здесь нет.

Однако в этом случае мы можем сделать исключение из моего общего правила закрытия вопросов "почему нет", потому что этот вопрос иллюстрирует важный момент о ковариации делегатов, о котором я никогда не писал ранее.

Я не принял решение оставить делегатов событий неизменными, но если бы я был в состоянии сделать это, я бы оставил делегатов событий неизменными по двум причинам.

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

Это довольно слабая причина. Более сильная причина также является более печальной.

Как мы знаем, универсальные типы делегатов могут быть сделаны ковариантными в своих типах возвращаемых данных и контравариантными в их типах параметров; мы обычно думаем о дисперсии в контексте совместимости присваивания. То есть, если у нас есть Func<Mammal, Mammal> в руках, мы можем присвоить его переменной типа Func<Giraffe, Animal> и знать, что основная функция всегда будет брать млекопитающее - потому что теперь он получит только жирафы - и всегда будут возвращать животное - потому что оно возвращает млекопитающих.

Но мы также знаем, что делегаты могут быть добавлены вместе; делегаты являются неизменяемыми, поэтому при добавлении двух делегатов вместе получается третий; сумма является последовательным составом слагаемых.

Полевые события реализуются с использованием суммирования делегатов; поэтому добавление обработчика к событию представляется как +=. (Я не большой поклонник этого синтаксиса, но мы застряли с ним сейчас.)

Хотя обе эти функции работают хорошо независимо друг от друга, они плохо работают в комбинации. Когда я реализовал дисперсию делегатов, наши тесты в короткие сроки обнаружили, что в CLR есть ряд ошибок, связанных с добавлением делегатов, когда базовые типы делегатов не совпадали из-за преобразований с поддержкой дисперсии. Эти ошибки существовали со времен CLR 2.0, но до С# 4.0 ни один основной язык не обнаруживал ошибок, не было написано ни одного теста для них и так далее.

К сожалению, я не помню, какие были репродукторы для ошибок; это было двенадцать лет назад, и я не знаю, есть ли еще какие-нибудь заметки на нем, спрятанные где-нибудь на диске.

В то время мы работали с командой CLR, чтобы попытаться устранить эти ошибки для следующей версии CLR, но они не считались достаточно приоритетными по сравнению с их риском. Многие типы, такие как IEnumerable<T> и IComparable<T> и т.д., Были сделаны вариантами в этих выпусках, как и типы Func и Action, но редко суммируются два несовпадающих Func с использованием преобразования вариантов. Но для делегатов мероприятия их единственная цель в жизни - сложить вместе; они будут добавляться вместе все время, и если бы они были вариантами, был бы риск раскрытия этих ошибок большому количеству пользователей.

Вскоре после С# 4 я потерял учет проблем и, честно говоря, не знаю, были ли они когда-нибудь решены. Попробуйте сложить несколько несовпадающих делегатов в различных комбинациях и посмотрите, не случится ли что-нибудь плохое!

Так что это хорошая, но прискорбная причина, по которой нельзя делать вариант делегатов событий в период выпуска С# 4.0. Есть ли еще веская причина, я не знаю. Вы должны спросить кого-то из команды CLR.