Delegate equality и методы типов-значений
Видели предыдущий пост? Как вам такое продолжение истории:
using System;
struct Foo {
public void M() { Console.Write("uups!"); }
}
class Bar {
static event Action E = delegate { };
static void Main() {
var foo = new Foo();
E += foo.M;
E -= foo.M;
E(); // ???
}
}
Отписки снова не происходит! Более того, снаружи такого события отписку такого метода произвести невозможно.
Дело тут в определении типа Foo
, являющегося типом-значением, и скрытом боксинге, происходящем при подписке и отписке. Для того, чтобы сделать делегат из метода уровня экземпляра, определённого для типа-значения, надо как-то в делегат сохранить это экземпляр значения (так же, как туда сохраняется this
при создании делегатов из методов уровня экземпляра, определённых в классах). Это нельзя сделать никак иначе, кроме как вызвав боксинг значения и “прикрепив” бокс к делегату, так как время жизни делегата часто неопределено и он должен создаваться на куче.
Тогда что происходит при отписке? Всё очень просто - создаётся ещё один бокс значения. И тут становится понятно, что невозможно выяснить, что два бокса были сделаны из одного и того же значения. Реализация Delegate.Equals
не должна и не полагается на типы-значения, определяющие свои понятия эквивалентности (переопределяющие Equals
/реализующие IEquatable<T>
), а руководствоваться понятием ссылочной эквивалентности, которого не прослеживается между двумя разными боксами одного значения. Именно поэтому, не смотря на равенство методов, подписанный на событие делегат не равен делегату, с помощью которого происходит попытка отписки. Делегатов, равных сходному просто не может больше существовать!