Ответ 1
Все сводится к: когда два делегата считаются одинаковыми для целей делегирования сложения/вычитания. Когда вы отказываетесь от подписки, она по существу использует логику от Delegate.Remove
, которая считает два делегата эквивалентными, если совпадают как теги .Target
, так и .Method
(по крайней мере, для простого случая делегата с единственным целевым методом; многоадресная рассылка сложнее описать). Итак: что такое .Method
и .Target
на лямбда (если мы компилируем его для делегата, а не для выражения)?
Компилятор действительно имеет здесь большую свободу, но происходит следующее:
- если лямбда включает замыкание по параметру или переменной, компилятор создает метод (метод) в классе, сгенерированном компилятором, который представляет контекст захвата (который также может включать токен
this
); целью является ссылка на этот экземпляр capture-context (который будет определяться областью захвата). - если лямбда не включает замыкание по параметру или переменной, но использует состояние для каждого экземпляра через
this
(неявный или явный), компилятор создает метод экземпляра (метод) текущего тип; целью является текущий экземпляр (this
) - иначе компилятор создает статический метод (метод), а целевой - нулевой (кстати, в этом случае он также включает в себя отличное поле для кэширования одного статического экземпляра делегата, поэтому в этом случае только один делегат создано на лямбда)
Однако, что он не делает, сравнивает множество лямбда с похожими лицами, чтобы уменьшить их. Итак, что я получаю, когда компилирую ваш код, статические методы два:
[CompilerGenerated]
private static void <Main>b__0(object s, string e)
{
Console.WriteLine("Bark: {0}", e);
}
[CompilerGenerated]
private static void <Main>b__2(object s, string e)
{
Console.WriteLine("Bark: {0}", e);
}
(Main
здесь только потому, что в моей тестовой установке эти lambdas находятся внутри метода Main
, но в конечном итоге компилятор может выбрать любые непроизносимые имена, которые он выбирает здесь)
Первый метод используется первой лямбда; второй метод используется второй лямбдой. Поэтому, в конечном счете, причина, по которой он не работает, заключается в том, что .Method
не соответствует.
В регулярных терминах С# это будет выглядеть так:
obj.SomeEvent += MethodOne;
obj.SomeEvent -= MethodTwo;
где MethodOne
и MethodTwo
имеют один и тот же код внутри них; он ничего не отписывает.
Было бы неплохо, если бы компилятор заметил это, но не требуется, и как таковое безопаснее, чем он не выбирает - это может означать, что разные компиляторы начинают производить очень разные результаты.
В качестве побочного примечания; это может быть очень сбивающим с толку, если он попытается разобраться, потому что у вас также будет проблема контекстов захвата - тогда будет так, что он "работал" в некоторых случаях, а не в других, - не будучи очевидным, наихудший возможный сценарий.