A ‘Proper’ WeakReference Class

June 4, 2011

tags: , , ,
2 comments

The WeakReference class that exists since .NET 1.0 can be used to wrap an object while not being the one keeping it alive. This may be useful in an “Observer” pattern scenario, where a client registers for some notification, but “forgets” to unregister. That means that with a normal reference, even if the client is no longer needed, it keeps getting notifications because the server object keeps a strong reference to the client.

Here’s a simple example:

  1. class BasicSubject {
  2.     List<INotify> _clients = new List<INotify>();
  3.  
  4.     public void Attach(INotify client) {
  5.         _clients.Add(client);
  6.     }
  7.  
  8.     public void DoSomething() {
  9.         // notify clients
  10.         foreach(var client in _clients)
  11.             client.Update();
  12.     }
  13.  
  14.     public void Detach(INotify client) {
  15.         _clients.Remove(client);
  16.     }
  17. }

 

A simple client (observer) may be implemented like so:

  1. class MyObserver : INotify {
  2.     public void Update() {
  3.         Console.WriteLine("Update received!");
  4.     }
  5. }

Now, we can test:

  1. static void TestBasicSubject() {
  2.         var subject = new BasicSubject();
  3.         var observer = new MyObserver();
  4.         subject.Attach(observer);
  5.  
  6.         subject.DoSomething();
  7.  
  8.         // not interested anymore
  9.         observer = null;
  10.         subject.DoSomething();    // still notified
  11.  
  12.         GC.Collect();
  13.         subject.DoSomething();    // still notified yet!
  14.     }

How many times would the observer be notified? Three times. The call in line (12) to GC.Collect is not enough, because the client is not garbage – the subject holds a strong reference to the client.

The solution is obvious – just call Detach on the subject and you’re good. The problem is that sometimes clients forget to detach, and sometimes it’s difficult to call it in the right moment – if the client is used from different threads, for instance, Detach needs to be called at the right time, i.e. not too soon.

The subject can be more defensive – not hold on to the observer too tightly using the WeakReference class. Here’s a revised subject:

  1. class BetterSubject {
  2.     List<WeakReference> _observers = new List<WeakReference>();
  3.  
  4.     public void Attach(INotify notify) {
  5.         _observers.Add(new WeakReference(notify));
  6.     }
  7.  
  8.     public void Detach(INotify notify) {
  9.         foreach(var wr in _observers)
  10.             if(wr.Target == notify) {
  11.                 _observers.Remove(wr);
  12.                 break;
  13.             }
  14.     }
  15.  
  16.     public void DoSomething() {
  17.         for(int i = 0; i < _observers.Count; i++) {
  18.             var notify = (INotify)_observers[i].Target;
  19.             if(notify == null) {    // garbage collected
  20.                 _observers.RemoveAt(i);
  21.                 i–;
  22.             }
  23.             else
  24.                 notify.Update();
  25.         }
  26.     }
  27. }

Every observer is wrapped in a WeakReference. The Target property points to the real object, or null, if it has since been garbage collected. Let’s test it:

  1. static void TestBetterSubject() {
  2.     BetterSubject subject = new BetterSubject();
  3.     MyObserver observer = new MyObserver();
  4.     subject.Attach(observer);
  5.  
  6.     subject.DoSomething();
  7.  
  8.     observer = null;
  9.     subject.DoSomething();    // notify
  10.     GC.Collect();
  11.  
  12.     subject.DoSomething();    // not notified!
  13. }

Now we only get two notifications! That’s because the subject is holding a weak reference to the observer, so cannot be the one keeping it alive if no other string references exist.

The WeakReference class has two “issues”:

1. It does not implement IDisposable, so the underlying GCHandle is freed only when it is garbage collected.

2. It’s not generic (somewhat understandable, as it exists since .NET 1.0)

We can fix both these issues by either deriving from WeakReference (it’s prepared for that, most of its members are virtual), or create our own implementation.

I decided to go for the latter, as the WeakReference is not immutable (it’s target can be replaced) and I like immutability these days. We can also simplify it a bit. Here one way to go about it:

  1. public sealed class WeakReference<T> : IDisposable where T : class {
  2.     GCHandle _handle;
  3.  
  4.     public WeakReference(T obj) {
  5.         _handle = GCHandle.Alloc(obj, GCHandleType.Weak);
  6.     }
  7.  
  8.     public T Target {
  9.         get { return _handle.IsAllocated ? _handle.Target as T : null; }
  10.     }
  11.  
  12.     public bool IsAlive {
  13.         get { return _handle.Target != null; }
  14.     }
  15.  
  16.     public void Dispose() {
  17.         _handle.Free();
  18.         GC.SuppressFinalize(this);
  19.     }
  20.  
  21.     ~WeakReference() {
  22.         _handle.Free();
  23.     }
  24.  
  25.     public static bool operator ==(WeakReference<T> a, WeakReference<T> b) {
  26.         return a.Equals(b);
  27.     }
  28.  
  29.     public static bool operator !=(WeakReference<T> a, WeakReference<T> b) {
  30.         return !(a == b);
  31.     }
  32.  
  33.     public override bool Equals(object obj) {
  34.         return _handle.Equals(((WeakReference<T>)obj)._handle);
  35.     }
  36.  
  37.     public override int GetHashCode() {
  38.         return _handle.GetHashCode();
  39.     }
  40. }

Now its usage is a bit more intuitive:

  1. class Subject {
  2.     List<WeakReference<INotify>> _observers = new List<WeakReference<INotify>>();
  3.  
  4.     public void Attach(INotify observer) {
  5.         _observers.Add(new WeakReference<INotify>(observer));
  6.     }
  7.  
  8.     public void DoSomething() {
  9.         for(int i = 0; i < _observers.Count; ++i) {
  10.             INotify notify = _observers[i].Target;
  11.             if(notify != null)
  12.                 notify.Update();
  13.             else {
  14.                 _observers[i].Dispose();
  15.                 _observers.RemoveAt(i);
  16.                 —i;
  17.             }
  18.         }
  19.     }
  20.  
  21.     public void Detach(INotify client) {
  22.         foreach(var observer in _observers) {
  23.             if(observer.Target == client) {
  24.                 observer.Dispose();
  25.                 _observers.Remove(observer);
  26.                 break;
  27.             }
  28.         }
  29.     }
  30. }

My WeakReference<T> is not serializable (the original WeakReference is), but that’s not  too difficult to add.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. eladkatzJune 5, 2011 ב 01:00

    I never knew a GCHanlde was under the covers in WeakReference. Good post!

    Reply
  2. ���å� ؔ��August 17, 2013 ב 11:39

    You will find consistently area for the leading.

    Reply