Слабые события в .NET?

В Java все переменные, которые вы объявляете, на самом деле являются «ссылками» на объекты (или примитивы), а не самими объектами.

При попытке выполнить один метод объекта , ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Тогда runtime сообщит вам об этом, выбросив исключение NullPointerException.

Ваша ссылка «указывает» на нуль, таким образом, «Null -> Pointer».

Объект живет в памяти виртуальной машины пространство и единственный способ доступа к нему - использовать ссылки this. Возьмем этот пример:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

И в другом месте вашего кода:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

Это важно знать - когда больше нет ссылок на объект (в пример выше, когда reference и otherReference оба указывают на null), тогда объект «недоступен». Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент VM освободит память, используемую этим объектом, и выделит другую.

26
задан tom7 6 July 2009 в 21:46
поделиться

9 ответов

Дастин Кэмпбелл из блога DidItWith.NET исследует несколько неудачных попыток создания слабых обработчиков событий, а затем продолжает показывать правильную, работающую и легковесную реализацию: Решение проблемы со слабыми обработчиками событий .

В идеале, Microsoft должна внедрить эту концепцию в сам язык. Примерно так:

Foo.Clicked += new weak EventHandler(...);

Если вы считаете, что эта функция важна для вас, пожалуйста, проголосуйте за нее здесь .

45
ответ дан Lennart 6 July 2009 в 21:46
поделиться
  • 1
    @klochner я не соглашаюсь, это решение, был точно, что я должен был... обосновать: Я хотел в общем назвать вышестоящий метод различного метода, но без потребности исказить каждого, что я хотел быть в состоянии сделать это для, таким образом, универсальный способ вызвать супер довольно полезен – Mike Stone 27 September 2010 в 19:32
  • 2
    @klochner я не соглашаюсь, это решение, был точно, что я должен был... обосновать: Я хотел в общем назвать вышестоящий метод различного метода, но без потребности исказить каждого, что я хотел быть в состоянии сделать это для, таким образом, универсальный способ вызвать супер довольно полезен – Mike Stone 27 September 2010 в 19:32
  • 3
    @klochner я не соглашаюсь, это решение, был точно, что я должен был... обосновать: Я хотел в общем назвать вышестоящий метод различного метода, но без потребности исказить каждого, что я хотел быть в состоянии сделать это для, таким образом, универсальный способ вызвать супер довольно полезен – Mike Stone 27 September 2010 в 19:32
  • 4
    @klochner я не соглашаюсь, это решение, был точно, что я должен был... обосновать: Я хотел в общем назвать вышестоящий метод различного метода, но без потребности исказить каждого, что я хотел быть в состоянии сделать это для, таким образом, универсальный способ вызвать супер довольно полезен – Mike Stone 27 September 2010 в 19:32
  • 5
    @klochner я не соглашаюсь, это решение, был точно, что я должен был... обосновать: Я хотел в общем назвать вышестоящий метод различного метода, но без потребности исказить каждого, что я хотел быть в состоянии сделать это для, таким образом, универсальный способ вызвать супер довольно полезен – Mike Stone 27 September 2010 в 19:32

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

Кредиты:
Г-н. Первоначальная реализация Кэмпбелла
Очень удобная функция приведения делегатов Эд Болла, ссылка может быть найдена в источнике

Обработчик и пара перегрузок, EventHander < E> и PropertyChangedEventHandler:


///  Basic weak event management. 
/// 
///  Weak allow objects to be garbage collected without having to unsubscribe
///  
///  Taken with some minor variations from:
///  http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
///  
///  use as class.theEvent +=new EventHandler<EventArgs>(instance_handler).MakeWeak((e) => class.theEvent -= e);
///  MakeWeak extension methods take an delegate to unsubscribe the handler from the event
/// 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;

namespace utils {

 /// <summary>
 /// Delegate of an unsubscribe delegate
 /// </summary>
 public delegate void UnregisterDelegate<H>(H eventHandler) where H : class;

 /// <summary>
 /// A handler for an event that doesn't store a reference to the source
 /// handler must be a instance method
 /// </summary>
 /// <typeparam name="T">type of calling object</typeparam>
 /// <typeparam name="E">type of event args</typeparam>
 /// <typeparam name="H">type of event handler</typeparam>
 public class WeakEventHandlerGeneric<T, E, H>
  where T : class
  where E : EventArgs 
  where H : class {

  private delegate void OpenEventHandler(T @this, object sender, E e);

  private delegate void LocalHandler(object sender, E e);

  private WeakReference m_TargetRef;
  private OpenEventHandler m_OpenHandler;
  private H m_Handler;
  private UnregisterDelegate<H> m_Unregister;

  public WeakEventHandlerGeneric(H eventHandler, UnregisterDelegate<H> unregister) {
   m_TargetRef = new WeakReference((eventHandler as Delegate).Target);
   m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), null, (eventHandler as Delegate).Method);
   m_Handler = CastDelegate(new LocalHandler(Invoke));
   m_Unregister = unregister;
  }

  private void Invoke(object sender, E e) {
   T target = (T)m_TargetRef.Target;

   if (target != null)
    m_OpenHandler.Invoke(target, sender, e);
   else if (m_Unregister != null) {
    m_Unregister(m_Handler);
    m_Unregister = null;
   }
  }

  /// <summary>
  /// Gets the handler.
  /// </summary>
  public H Handler {
   get { return m_Handler; }
  }

  /// <summary>
  /// Performs an implicit conversion from <see cref="PR.utils.WeakEventHandler&lt;T,E&gt;"/> to <see cref="System.EventHandler&lt;E&gt;"/>.
  /// </summary>
  /// <param name="weh">The weh.</param>
  /// <returns>The result of the conversion.</returns>
  public static implicit operator H(WeakEventHandlerGeneric<T, E, H> weh) {
   return weh.Handler;
  }

  /// <summary>
  /// Casts the delegate.
  /// Taken from
  /// http://jacobcarpenters.blogspot.com/2006/06/cast-delegate.html
  /// </summary>
  /// <param name="source">The source.</param>
  /// <returns></returns>
  public static H CastDelegate(Delegate source) {
   if (source == null) return null;

   Delegate[] delegates = source.GetInvocationList();
   if (delegates.Length == 1)
    return Delegate.CreateDelegate(typeof(H), delegates[0].Target, delegates[0].Method) as H;

   for (int i = 0; i < delegates.Length; i++)
    delegates[i] = Delegate.CreateDelegate(typeof(H), delegates[i].Target, delegates[i].Method);

   return Delegate.Combine(delegates) as H;
  }
 }

 #region Weak Generic EventHandler<Args> handler

 /// <summary>
 /// An interface for a weak event handler
 /// </summary>
 /// <typeparam name="E"></typeparam>
 public interface IWeakEventHandler<E> where E : EventArgs {
  EventHandler<E> Handler { get; }
 }

 /// <summary>
 /// A handler for an event that doesn't store a reference to the source
 /// handler must be a instance method
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <typeparam name="E"></typeparam>
 public class WeakEventHandler<T, E> : WeakEventHandlerGeneric<T, E, EventHandler<E>>, IWeakEventHandler<E>
  where T : class
  where E : EventArgs {

  public WeakEventHandler(EventHandler<E> eventHandler, UnregisterDelegate<EventHandler<E>> unregister) 
   : base(eventHandler, unregister) { }
 }

 #endregion

 #region Weak PropertyChangedEvent handler

 /// <summary>
 /// An interface for a weak event handler
 /// </summary>
 /// <typeparam name="E"></typeparam>
 public interface IWeakPropertyChangedEventHandler {
  PropertyChangedEventHandler Handler { get; }
 }

 /// <summary>
 /// A handler for an event that doesn't store a reference to the source
 /// handler must be a instance method
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <typeparam name="E"></typeparam>
 public class WeakPropertyChangeHandler<T> : WeakEventHandlerGeneric<T, PropertyChangedEventArgs, PropertyChangedEventHandler>, IWeakPropertyChangedEventHandler
  where T : class {

  public WeakPropertyChangeHandler(PropertyChangedEventHandler eventHandler, UnregisterDelegate<PropertyChangedEventHandler> unregister) 
   : base(eventHandler, unregister) {}
 }

 #endregion

 /// <summary>
 /// Utilities for the weak event method
 /// </summary>
 public static class WeakEventExtensions {

  private static void CheckArgs(Delegate eventHandler, Delegate unregister) {
   if (eventHandler == null) throw new ArgumentNullException("eventHandler");
   if (eventHandler.Method.IsStatic || eventHandler.Target == null) throw new ArgumentException("Only instance methods are supported.", "eventHandler");
  }

  private static object GetWeakHandler(Type generalType, Type[] genericTypes, Type[] constructorArgTypes, object[] constructorArgs) {
   var wehType = generalType.MakeGenericType(genericTypes);
   var wehConstructor = wehType.GetConstructor(constructorArgTypes);
   return wehConstructor.Invoke(constructorArgs);
  }

  /// <summary>
  /// Makes a property change handler weak
  /// </summary>
  /// <typeparam name="E"></typeparam>
  /// <param name="eventHandler">The event handler.</param>
  /// <param name="unregister">The unregister.</param>
  /// <returns></returns>
  public static PropertyChangedEventHandler MakeWeak(this PropertyChangedEventHandler eventHandler, UnregisterDelegate<PropertyChangedEventHandler> unregister) {
   CheckArgs(eventHandler, unregister);

   var generalType = typeof (WeakPropertyChangeHandler<>);
   var genericTypes = new[] {eventHandler.Method.DeclaringType};
   var constructorTypes = new[] { typeof(PropertyChangedEventHandler), typeof(UnregisterDelegate<PropertyChangedEventHandler>) };
   var constructorArgs = new object[] {eventHandler, unregister};

   return ((IWeakPropertyChangedEventHandler) GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler;
  }

  /// <summary>
  /// Makes a generic handler weak
  /// </summary>
  /// <typeparam name="E"></typeparam>
  /// <param name="eventHandler">The event handler.</param>
  /// <param name="unregister">The unregister.</param>
  /// <returns></returns>
  public static EventHandler<E> MakeWeak<E>(this EventHandler<E> eventHandler, UnregisterDelegate<EventHandler<E>> unregister) where E : EventArgs {
   CheckArgs(eventHandler, unregister);

   var generalType = typeof(WeakEventHandler<,>);
   var genericTypes = new[] { eventHandler.Method.DeclaringType, typeof(E) };
   var constructorTypes = new[] { typeof(EventHandler<E>), typeof(UnregisterDelegate<EventHandler<E>>) };
   var constructorArgs = new object[] { eventHandler, unregister };

   return ((IWeakEventHandler<E>)GetWeakHandler(generalType, genericTypes, constructorTypes, constructorArgs)).Handler;
  }
 }
}

Модульные тесты:


using System.ComponentModel;
using NUnit.Framework;
using System.Collections.Generic;
using System;

namespace utils.Tests {
 [TestFixture]
 public class WeakEventTests {

  #region setup/teardown

  [TestFixtureSetUp]
  public void SetUp() {
   testScenarios.Add(SetupTestGeneric);
   testScenarios.Add(SetupTestPropChange);
  }

  [TestFixtureTearDown]
  public void TearDown() {

  }

  #endregion

  #region tests

  private List<Action<bool>> testScenarios = new List<Action<bool>>();

  private IEventSource source;
  private WeakReference sourceRef;

  private IEventConsumer consumer;
  private WeakReference consumerRef;

  private IEventConsumer consumer2;
  private WeakReference consumerRef2;

  [Test]
  public void ConsumerSourceTest() {
   foreach(var a in testScenarios) {
    a(false);
    ConsumerSourceTestMethod();
   }
  }

  private void ConsumerSourceTestMethod() {
   Assert.IsFalse(consumer.eventSet);
   source.Fire();
   Assert.IsTrue(consumer.eventSet);
  }

  [Test]
  public void ConsumerLinkTest() {
   foreach (var a in testScenarios) {
    a(false);
    ConsumerLinkTestMethod();
   }
  }

  private void ConsumerLinkTestMethod() {
   consumer = null;
   GC.Collect();
   Assert.IsFalse(consumerRef.IsAlive);
   Assert.IsTrue(source.InvocationCount == 1);
   source.Fire();
   Assert.IsTrue(source.InvocationCount == 0);
  }

  [Test]
  public void ConsumerLinkTestDouble() {
   foreach (var a in testScenarios) {
    a(true);
    ConsumerLinkTestDoubleMethod();
   }
  }

  private void ConsumerLinkTestDoubleMethod() {
   consumer = null;
   GC.Collect();
   Assert.IsFalse(consumerRef.IsAlive);
   Assert.IsTrue(source.InvocationCount == 2);
   source.Fire();
   Assert.IsTrue(source.InvocationCount == 1);
   consumer2 = null;
   GC.Collect();
   Assert.IsFalse(consumerRef2.IsAlive);
   Assert.IsTrue(source.InvocationCount == 1);
   source.Fire();
   Assert.IsTrue(source.InvocationCount == 0);
  }

  [Test]
  public void ConsumerLinkTestMultiple() {
   foreach (var a in testScenarios) {
    a(true);
    ConsumerLinkTestMultipleMethod();
   }
  }

  private void ConsumerLinkTestMultipleMethod() {
   consumer = null;
   consumer2 = null;
   GC.Collect();
   Assert.IsFalse(consumerRef.IsAlive);
   Assert.IsFalse(consumerRef2.IsAlive);
   Assert.IsTrue(source.InvocationCount == 2);
   source.Fire();
   Assert.IsTrue(source.InvocationCount == 0);
  }

  [Test]
  public void SourceLinkTest() {
   foreach (var a in testScenarios) {
    a(false);
    SourceLinkTestMethod();
   }
  }

  private void SourceLinkTestMethod() {
   source = null;
   GC.Collect();
   Assert.IsFalse(sourceRef.IsAlive);
  }

  [Test]
  public void SourceLinkTestMultiple() {
   SetupTestGeneric(true);
   foreach (var a in testScenarios) {
    a(true);
    SourceLinkTestMultipleMethod();
   }
  }

  private void SourceLinkTestMultipleMethod() {
   source = null;
   GC.Collect();
   Assert.IsFalse(sourceRef.IsAlive);
  }

  #endregion

  #region test helpers

  public void SetupTestGeneric(bool both) {
   source = new EventSourceGeneric();
   sourceRef = new WeakReference(source);

   consumer = new EventConsumerGeneric((EventSourceGeneric)source);
   consumerRef = new WeakReference(consumer);

   if (both) {
    consumer2 = new EventConsumerGeneric((EventSourceGeneric)source);
    consumerRef2 = new WeakReference(consumer2);
   }
  }

  public void SetupTestPropChange(bool both) {
   source = new EventSourcePropChange();
   sourceRef = new WeakReference(source);

   consumer = new EventConsumerPropChange((EventSourcePropChange)source);
   consumerRef = new WeakReference(consumer);

   if (both) {
    consumer2 = new EventConsumerPropChange((EventSourcePropChange)source);
    consumerRef2 = new WeakReference(consumer2);
   }
  }

  public interface IEventSource {
   int InvocationCount { get; }
   void Fire();
  }

  public class EventSourceGeneric : IEventSource {
   public event EventHandler<EventArgs> theEvent;
   public int InvocationCount {
    get { return (theEvent != null)? theEvent.GetInvocationList().Length : 0; }
   }
   public void Fire() {
    if (theEvent != null) theEvent(this, EventArgs.Empty);
   }
  }

  public class EventSourcePropChange : IEventSource {
   public event PropertyChangedEventHandler theEvent;
   public int InvocationCount {
    get { return (theEvent != null) ? theEvent.GetInvocationList().Length : 0; }
   }
   public void Fire() {
    if (theEvent != null) theEvent(this, new PropertyChangedEventArgs(""));
   }
  }

  public interface IEventConsumer {
   bool eventSet { get; }
  }

  public class EventConsumerGeneric : IEventConsumer {
   public bool eventSet { get; private set; }
   public EventConsumerGeneric(EventSourceGeneric sourceGeneric) {
    sourceGeneric.theEvent +=new EventHandler<EventArgs>(source_theEvent).MakeWeak((e) => sourceGeneric.theEvent -= e);
   }
   public void source_theEvent(object sender, EventArgs e) {
    eventSet = true;
   }
  }

  public class EventConsumerPropChange : IEventConsumer {
   public bool eventSet { get; private set; }
   public EventConsumerPropChange(EventSourcePropChange sourcePropChange) {
    sourcePropChange.theEvent += new PropertyChangedEventHandler(source_theEvent).MakeWeak((e) => sourcePropChange.theEvent -= e);
   }
   public void source_theEvent(object sender, PropertyChangedEventArgs e) {
    eventSet = true;
   }
  }

  #endregion
 }
}
10
ответ дан Egor 6 July 2009 в 21:46
поделиться
  • 1
    Сложный для определения однажды, простой назвать его большим количеством времен. That' s лучше, чем наоборот. – Grant Hutchins 16 September 2011 в 16:10
  • 2
    Сложный для определения однажды, простой назвать его большим количеством времен. That' s лучше, чем наоборот. – Grant Hutchins 16 September 2011 в 16:10
  • 3
    Сложный для определения однажды, простой назвать его большим количеством времен. That' s лучше, чем наоборот. – Grant Hutchins 16 September 2011 в 16:10
  • 4
    Сложный для определения однажды, простой назвать его большим количеством времен. That' s лучше, чем наоборот. – Grant Hutchins 16 September 2011 в 16:10
  • 5
    Сложный для определения однажды, простой назвать его большим количеством времен. That' s лучше, чем наоборот. – Grant Hutchins 16 September 2011 в 16:10

Использование рекомендованного шаблона Dispose () , где вы считаете события управляемым ресурсом для очистки, должно обрабатывать это. Объект A должен отменить свою регистрацию в качестве прослушивателя событий от объекта B, когда он расположен ...

1
ответ дан Arnshea 6 July 2009 в 21:46
поделиться

Подход Дастина Кэмпбелла уже превосходен. Единственное, что осталось - сохранить решение, интегрированное в .NET, - это действительно простой способ создания действительно общих обработчиков слабых событий:

http://puremsil.wordpress.com/2010/05/03 / родовой-слабого EVENT-обработчики /

0
ответ дан Stefan Dragnev 6 July 2009 в 21:46
поделиться
  • 1
    Хорошо, я был видом предположения, что OP говорил о целом пути URI, не только единственном сегменте. По крайней мере, URI shorteners обычно работает в способе http://domain.foo/<shortenedpart> где это doesn' t должен быть ограничен единственным цементом. – Joey 12 January 2011 в 14:24
  • 2
    Хорошо, я был видом предположения, что OP говорил о целом пути URI, не только единственном сегменте. По крайней мере, URI shorteners обычно работает в способе http://domain.foo/<shortenedpart> где это doesn' t должен быть ограничен единственным цементом. – Joey 12 January 2011 в 14:24
  • 3
    Хорошо, я был видом предположения, что OP говорил о целом пути URI, не только единственном сегменте. По крайней мере, URI shorteners обычно работает в способе http://domain.foo/<shortenedpart> где это doesn' t должен быть ограничен единственным цементом. – Joey 12 January 2011 в 14:24
  • 4
    Хорошо, я был видом предположения, что OP говорил о целом пути URI, не только единственном сегменте. По крайней мере, URI shorteners обычно работает в способе http://domain.foo/<shortenedpart> где это doesn' t должен быть ограничен единственным цементом. – Joey 12 January 2011 в 14:24
  • 5
    Хорошо, я был видом предположения, что OP говорил о целом пути URI, не только единственном сегменте. По крайней мере, URI shorteners обычно работает в способе http://domain.foo/<shortenedpart> где это doesn' t должен быть ограничен единственным цементом. – Joey 12 January 2011 в 14:24

Реализация Дастина работает только с делегатами EventHandler. Если вы перейдете к CodePlex, то появится проект под названием Sharp Observation , в котором автор создал очень хорошего поставщика слабых делегатов. Он реализован в MSIL и значительно быстрее и гибче.

... что, до тех пор, пока Microsoft не осуществит слабые события изначально, придется делать.

0
ответ дан Mark 6 July 2009 в 21:46
поделиться

Важная деталь:

Реализация Дастина устраняет сильную ссылку, введенную обработчиком событий, но может привести к новой утечке памяти (по крайней мере, если не уделять достаточно внимания).

Поскольку незарегистрированный обратный вызов не рассматривается как слабый обработчик событий, он может содержать сильную ссылку на некоторый объект. Это зависит от того, объявили ли вы незарегистрированный обратный вызов в классе подписчиков Event или нет.

Если этого не сделать, обратный вызов будет связан со ссылкой на экземпляр окружающего класса. Вот пример того, на что я ссылаюсь: после объявления незарегистрированного обратного вызова он будет содержать ссылку на экземпляр класса Program:

public class EventSource
        {
            public event EventHandler<EventArgs> Fired
        }
}
 public class EventSubscriber
    {
        public void OnEventFired(object sender, EventArgs) { ; }
    }

 public class Program {

    public void Main()
    {
    var source = new EventSource();
    var subscriber = new EventSubscriber();
    source.Fired += new WeakEventHandler<EventSubscriber, EventArgs>(subscriber.OnEventFired, handler => source.Fired -= handler);
    }
}
0
ответ дан Dennis Kassel 6 July 2009 в 21:46
поделиться

Существует также решение, которое работает в Silverlight / WP7, в котором вместо выражения MSIL используются выражения Linq.

http://socialeboladev.wordpress.com/2012/09/23/weak-event-implementation-that-works-for-any-event-type/

0
ответ дан Shahar Prish 6 July 2009 в 21:46
поделиться

Какие преимущества имеет реализация Дастина по сравнению с классом WPF WeakEventManager, который просто оборачивает целевой объект, а также делегат в слабую ссылку:

public Listener(object target, Delegate handler)
  {
       this._target = new WeakReference(target);
       this._handler = new WeakReference((object) handler);
  }

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

public void Invoke(object sender, E e)
        {
            T target = (T)m_TargetRef.Target;

            if (target != null)
                m_OpenHandler(target, sender, e);

Это также позволяет использовать аномимные методы вместо метода экземпляра (который, кажется, также недостаток реализации Дастина).

0
ответ дан Dennis Kassel 6 July 2009 в 21:46
поделиться

Будьте осторожны при использовании слабых реализаций событий. Слабые события, по-видимому, снимают с подписчика ответственность за отказ от подписки на событие (или сообщение). В этом случае обработчики событий могут вызываться даже после того, как подписчик «выходит из области видимости». Возьмите подписчика, который явно не отписывается и становится сборщиком мусора, но еще не сборщиком мусора. Слабый менеджер событий не сможет обнаружить это состояние и поэтому будет вызывать обработчик событий этого подписчика. Это может привести к неожиданным побочным эффектам.

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

0
ответ дан Ladi 6 July 2009 в 21:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: