Ayende ne ha parlato proprio in questi giorni, ed io è qualche settimana che giro intorno… anche se lo scenario è diverso.
Unoa dei problemi delle differenze tra Wpf e Windows Forms è che per il mondo Wpf i concetti di IDisposable e di IComponent sono del tutto irrilevanti, del resto non ci sono risorse, aka handle ad esempio, da rilasciare.
In Windows Forms sfruttavo il fatto di poter realizzare un IComponent e avere gratis la chiamata a Dispose nel momento in cui veniva fatta la Dispose del componente contenitore, ad esempio la Form. In molti casi deviavo leggermente dal Dispose Pattern sfruttandolo per liberare anche risorse managed come ad esempio event handler.
Questo giochetto non funziona più! Osservate questo esempio:
var memento = new ChangeTrackingService();

var command = DelegateCommand.Create()
    .OnCanExecute( o => memento.IsChanged )
    .OnExecute( o => { /* save pending changes */ } )
    .TriggerUsing( MementoObserver.Monitor( memento ) );
Il “.net memory leak” è servito :-)
Un monitor altro non è che un Observer che è capace di osservare uno specifico comportamento, nell’esempio un MementoObserver è un observer che è in grado di tenere d’occhio lo stato di un memento, more to come, e di triggherare la valutazione di un ICommand (CanExecuteChanged) al variare dello stato del memento, uno schiavetto con le mani in pasta da entrambe le parti :-)
Il problema sono proprio le mani in pasta da entrambe le parti… ampliamo un attimo lo scenario:
  • Il memento sta mementando :-) una collection;
  • La collection è una lista di qualcosa in binding con una ItemsControl di Wpf;
  • La collection, per motivi storici e di compatibilità con Windows Forms, implementa IComponent;
  • Sulla UI c’è un Button il cui Command è in binding con il command nel ViewModel;
  • Il memento ha un trattamento speciale per gli IComponent;
  • Il memento notifica le variazioni di stato con un evento TrackingStateChanged;
  • Il MementoObserver ascolta l’evento di cui sopra, e trigghera il command;
  • Quando un IComponent notifica di essere Disposed il Memento smette di tenerne traccia e notifica la sua variazione di stato, TrackingStateChanged, l’observer se ne accorge e trigghera il command;
ok, adesso piazzate il tutto nella coda di finalizzazione del GC che giustamente gira in un thread diverso… patatrack :-) il tutto in maniera casuale perchè, a seconda dell’ordine in vengono finalizzate le varie reference, l’ultimo punto della lista di cui sopra potrebbe cercare di accedere alla UI dal Thread del Finalizer –> aka CrossThread Exception.
WeakReference
la salvezza è usare una WeakReference per tutti quegli scenari in cui sia necessario avere una reference a qualcosa su cui non si ha un controllo diretto. In questo caso infatti il MementoObserver deriva da una classe astratta che incapsula il concetto di WeakReference all’elemento monitored e si preoccupa di gestirne il lifetime garantendo la consistenza.
.m