Ответ 1
В вашем случае все в порядке. Это объект, который публикует события, которые сохраняют цели обработчиков событий. Поэтому, если у меня есть:
publisher.SomeEvent += target.DoSomething;
то publisher
имеет ссылку на target
, но не наоборот.
В вашем случае издатель будет иметь право на сбор мусора (если нет других ссылок на него), поэтому факт, что он получил ссылку на целевые объекты обработчика событий, не имеет значения.
Трудным случаем является то, что издатель долгоживущий, но подписчики не хотят быть - в этом случае вам нужно отменить подписку обработчиков. Например, предположим, что у вас есть служба передачи данных, которая позволяет вам подписываться на асинхронные уведомления об изменениях в пропускной способности, а объект службы передачи является долговечным. Если мы это сделаем:
BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;
(Фактически вы хотите использовать блок finally, чтобы убедиться, что вы не пропустите обработчик событий.) Если мы не отменили подписку, значит, BandwidthUI
будет жить как минимум до тех пор, пока служба передачи.
Лично я редко сталкиваюсь с этим - обычно, если я подписываюсь на событие, цель этого события живет, по крайней мере, до тех пор, пока издатель - форма будет длиться, пока кнопка, которая находится на ней, например. Это стоит знать об этой потенциальной проблеме, но я думаю, что некоторые люди беспокоятся об этом, когда им это не нужно, потому что они не знают, в каком направлении идут ссылки.
EDIT: Это ответ на комментарий Джонатана Дикинсона. Во-первых, рассмотрите документы для Delegate.Equals(object), которые явно дают поведение равенства.
Во-вторых, здесь короткая, но полная программа, показывающая отмену подписки:
using System;
public class Publisher
{
public event EventHandler Foo;
public void RaiseFoo()
{
Console.WriteLine("Raising Foo");
EventHandler handler = Foo;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
else
{
Console.WriteLine("No handlers");
}
}
}
public class Subscriber
{
public void FooHandler(object sender, EventArgs e)
{
Console.WriteLine("Subscriber.FooHandler()");
}
}
public class Test
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.Foo += subscriber.FooHandler;
publisher.RaiseFoo();
publisher.Foo -= subscriber.FooHandler;
publisher.RaiseFoo();
}
}
Результаты:
Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers
(Протестировано на Mono и .NET 3.5SP1.)
Дальнейшее редактирование:
Это доказывает, что издатель событий может быть собран, пока есть ссылки на подписчика.
using System;
public class Publisher
{
~Publisher()
{
Console.WriteLine("~Publisher");
Console.WriteLine("Foo==null ? {0}", Foo == null);
}
public event EventHandler Foo;
}
public class Subscriber
{
~Subscriber()
{
Console.WriteLine("~Subscriber");
}
public void FooHandler(object sender, EventArgs e) {}
}
public class Test
{
static void Main()
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.Foo += subscriber.FooHandler;
Console.WriteLine("No more refs to publisher, "
+ "but subscriber is alive");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("End of Main method. Subscriber is about to "
+ "become eligible for collection");
GC.KeepAlive(subscriber);
}
}
Результаты (в .NET 3.5SP1; Mono, похоже, выглядит немного странно здесь. Посмотрите, что некоторое время):
No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber