Partiamo da un caso tanto semplice quanto comune: Una classe EventSource che genera un evento EventTriggered sottoscritto da un altra classe EventConsumer.

EventSource:

   1:  class EventSource
   2:    {
   3:      public event EventHandler EventTriggered;    
   4:   
   5:      public void DoRaise(){
   6:        if (EventTriggered != null)EventTriggered(this, EventArgs.Empty);
   7:      }
   8:    }

EventConsumer:

   1:  class EventConsumer
   2:    {
   3:      public EventConsumer(EventSource source)
   4:      {
   5:        source.EventTriggered += new EventHandler(source_EventTriggered);
   6:      }
   7:   
   8:      ~EventConsumer()
   9:      {
  10:        Console.WriteLine("Object disposed");
  11:      }
  12:   
  13:      void source_EventTriggered(object sender, EventArgs e)
  14:      {
  15:        //Some stuff here...
  16:      }   
  17:    }

Codice che gestisce EventSource e EventConsumer:

   1:   class Program
   2:    {
   3:      static void Main(string[] args)
   4:      {
   5:        EventSource source = new EventSource();
   6:        EventConsumer consumer = new EventConsumer(source);
   7:   
   8:        consumer = null;
   9:        
  10:        GC.Collect();
  11:        GC.WaitForPendingFinalizers();
  12:        Console.ReadLine();
  13:      }
  14:    }

Eseguendo il codice è facile accorgersi che la classe che ha sottoscritto l'evento, pur essendo impostata a null non viene rilasciata dal garbage collector, questo a causa del riferimento contenuto nella collezione di event targets della classe EventSource. Il tutto si risolve de-registrando l'evento prima di porre a null l'istanza di consumer.
Purtroppo in alcuni scenari questo non è facilmente realizzabile in quanto non è facile determinare se ci si trova in una condizione tale per la quale è effettivamente possibile de-registrare l'evento e questo causa un inutile presenza in memoria di oggetti e/o un inutile sopravvivenza degli stessi fino a quando la fonte dell'evento non viene rilasciata.
Il pattern WeakEvent ha come obiettivo quello di fornire un architettura event-based eliminando il legame sorgente-gestore.
L'implementazione è descritta a questo indirizzo e fondamentalmente vede come attori principali la classe WeakEventManager e l'interfaccia IWeakEventListener entrambe in WindowsBase.dll

EventTriggeredManager

   1:    class EventTriggeredManager:WeakEventManager
   2:    {
   3:      protected override void StartListening(object source)
   4:      {
   5:        EventSource src = source as EventSource;
   6:        Debug.Assert(src != null);
   7:        src.EventTriggered += new EventHandler(OnEventTriggered);
   8:      }
   9:      protected override void StopListening(object source)
  10:      {
  11:        EventSource src = source as EventSource;
  12:        Debug.Assert(src != null);
  13:        src.EventTriggered -= new EventHandler(OnEventTriggered);
  14:      }
  15:   
  16:      public static void AddListener(EventSource source, IWeakEventListener listener)
  17:      {
  18:        EventTriggeredManager.CurrentManager.ProtectedAddListener(source, listener);
  19:      }
  20:      public static void RemoveListener(EventSource source, IWeakEventListener listener)
  21:      {
  22:        EventTriggeredManager.CurrentManager.ProtectedRemoveListener(source, listener);
  23:      }
  24:   
  25:      void OnEventTriggered(object sender, EventArgs e)
  26:      {
  27:        base.DeliverEvent(sender, e);
  28:      }
  29:   
  30:      private static EventTriggeredManager CurrentManager
  31:      {
  32:        get
  33:        {
  34:          Type type = typeof(EventTriggeredManager);
  35:          EventTriggeredManager manager = (EventTriggeredManager)WeakEventManager.GetCurrentManager(type);
  36:          if (manager == null)
  37:          {
  38:            manager = new EventTriggeredManager();
  39:            WeakEventManager.SetCurrentManager(type, manager);
  40:          }
  41:          return manager;
  42:        }
  43:      }
  44:    }

In breve:

  • Bisogna implementare un metodo CurrentManager che, in base al tipo passatogli, ritorni il relativo gestore "weak" e, nel caso non esista, lo crei e lo assegni usando SetCurrentManager.
  • Implementare due metodi Statici AddListener e RemoveListener che verranno usati per registrare/deregistrare l'evento.
  • Implementare i metodi astratti StartListening e StopListening che verrano richiamati da AddListener e RemoveListener sottoscrivendo l'evento che vogliamo gestire delegando a DeliverEvent il compito di scatenare l'evento.

Non è ancora finita..., WeakEventManager è in grado di consegnare l'evento esclusivamente a oggetti che implementano IWeakEventListener, ecco quindi una versione modificata di EventConsumer:

   1:    class EventConsumer : IWeakEventListener
   2:    {
   3:      ~EventConsumer()
   4:      {
   5:        Console.WriteLine("Object disposed");
   6:      }
   7:   
   8:      void source_EventTriggered(object sender, EventArgs e)
   9:      {
  10:        //Some stuff here...
  11:      }    
  12:   
  13:      public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  14:      {
  15:        if (managerType == typeof(EventTriggeredManager))
  16:        {
  17:          //Some handling here ...
  18:          return true;
  19:        }
  20:        else
  21:          return false;      
  22:      }   
  23:    }

L'evento si trasforma nell' invocazionde del metodo ReceiveWeakEvent il quale deve ritornare true se ha correttamente gestito l'evento oppure false in qualsiasi altro caso (viene generata un eccezione da parte di WeakEventManager a segnalare che si è passato un tipo non compatibile con il WeakEventManager associato)

Il codice che utilizza Source e Listener in questo caso diventa:

   1:     static void Main(string[] args)
   2:      {
   3:        EventSource source = new EventSource();
   4:        EventConsumer consumer = new EventConsumer(source);
   5:        EventTriggeredManager.AddListener(source, consumer);
   6:        //source.DoRaise(); //genero l'evento..
   7:        consumer = null;
   8:        
   9:        GC.Collect();
  10:        GC.WaitForPendingFinalizers();
  11:        Console.ReadLine();
  12:      }

Ottenendo come risultato finale il fatto che l'oggetto verrà correttamente richiamato dal GC malgrado sia connesso (in modo Weak) all' evento EventTriggered.