Code Listings

Chapter 12: Disposal and Garbage Collection

Calling Dispose from a finalizer:

class Test : IDisposable
{
  public void Dispose()             // NOT virtual
  {
    Dispose (true);
    GC.SuppressFinalize (this);     // Prevent finalizer from running.
  }

  protected virtual void Dispose (bool disposing)
  {
    if (disposing)
    {
      // Call Dispose() on other objects owned by this instance.
      // You can reference other finalizable objects here.
      // ...
    }

    // Release unmanaged resources owned by (just) this object.
    // ...
  }

  ˜Test()
  {
    Dispose (false);
  }
}

Resurrection - deleting temporary file

public class TempFileRef
{
  public readonly string FilePath;
  public TempFileRef (string filePath) { FilePath = filePath; }

  ~TempFileRef() { File.Delete (FilePath); }
}

Better solution:

public class TempFileRef
{
  static ConcurrentQueue<TempFileRef> _failedDeletions
    = new ConcurrentQueue<TempFileRef>();

  public readonly string FilePath;
  public Exception DeletionError { get; private set; }

  public TempFileRef (string filePath) { FilePath = filePath; }

  ~TempFileRef()
  {
    try { File.Delete (FilePath); }
    catch (Exception ex)
    {
      DeletionError = ex;
      _failedDeletions.Enqueue (this);   // Resurrection
    }
  }
}

GC.ReRegisterForFinalize

public class TempFileRef
{
  public readonly string FilePath;
  int _deleteAttempt;

  public TempFileRef (string filePath) { FilePath = filePath; }

  ~TempFileRef()
  {
    try { File.Delete (FilePath); }
    catch
    {
      if (_deleteAttempt++ < 3) GC.ReRegisterForFinalize (this);
    }
  }
}

Managed Memory Leak

class Host
{
  public event EventHandler Click;
}

class Client
{
  Host _host;
  public Client (Host host)
  {
    _host = host;
    _host.Click += HostClicked;
  }

  void HostClicked (object sender, EventArgs e) { ... }
}

class Test
{
  static Host _host = new Host();

  public static void CreateClients()
  {
    Client[] clients = Enumerable.Range (0, 1000)
     .Select (i => new Client (_host))
     .ToArray();

    // Do something with clients ... 
  }
}

Timers and memory leaks:

using System.Timers;

class Foo
{
  Timer _timer;
  
  Foo() 
  {
    _timer = new System.Timers.Timer { Interval = 1000 };
    _timer.Elapsed += tmr_Elapsed;
    _timer.Start();
  }

  void tmr_Elapsed (object sender, ElapsedEventArgs e) { ... }
}
class Foo : IDisposable
{
  ...
  public void Dispose() { _timer.Dispose(); }
}
static void Main()
{
  var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000);
  GC.Collect();
  System.Threading.Thread.Sleep (10000);    // Wait 10 seconds 
}

static void TimerTick (object notUsed) { Console.WriteLine ("tick"); }
using (var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000))
{
  GC.Collect();
  System.Threading.Thread.Sleep (10000);    // Wait 10 seconds 
}

Weak references:

var sb = new StringBuilder ("this is a test");
var weak = new WeakReference (sb);
Console.WriteLine (weak.Target);     // This is a test
var weak = new WeakReference (new StringBuilder ("weak"));
Console.WriteLine (weak.Target);   // weak
GC.Collect();
Console.WriteLine (weak.Target);   // (nothing)
class Widget
{
  static List<WeakReference> _allWidgets = new List<WeakReference>();

  public readonly string Name;

  public Widget (string name)
  {
    Name = name;
    _allWidgets.Add (new WeakReference (this));
  }

  public static void ListAllWidgets()
  {
    foreach (WeakReference weak in _allWidgets)
    {
      Widget w = (Widget)weak.Target;
      if (w != null) Console.WriteLine (w.Name);
    }
  }
}

Weak references and events

public class WeakDelegate<TDelegate> where TDelegate : class
{
  class MethodTarget
  {
    public readonly WeakReference Reference;
    public readonly MethodInfo Method;

    public MethodTarget (Delegate d)
    {
      Reference = new WeakReference (d.Target);
      Method = d.Method;
    }
  }

  List<MethodTarget> _targets = new List<MethodTarget>();

  public WeakDelegate()
  {
    if (!typeof (TDelegate).IsSubclassOf (typeof (Delegate)))
      throw new InvalidOperationException
        ("TDelegate must be a delegate type");
  }

  public void Combine (TDelegate target)
  {
    if (target == null) return;

    foreach (Delegate d in (target as Delegate).GetInvocationList())
      _targets.Add (new MethodTarget (d));
  }

  public void Remove (TDelegate target)
  {
    if (target == null) return;
    foreach (Delegate d in (target as Delegate).GetInvocationList())
    {
      MethodTarget mt = _targets.Find (w => 
        d.Target.Equals (w.Reference.Target) && 
        d.Method.MethodHandle.Equals (w.Method.MethodHandle));

      if (mt != null) _targets.Remove (mt);
    }
  }

  public TDelegate Target
  {
    get
    {
      var deadRefs = new List<MethodTarget>();
      Delegate combinedTarget = null;

      foreach (MethodTarget mt in _targets.ToArray())
      {
        WeakReference target = mt.Reference;
        if (target != null && target.IsAlive)
        {
          var newDelegate = Delegate.CreateDelegate (
            typeof (TDelegate), mt.Reference.Target, mt.Method);
            
          combinedTarget = Delegate.Combine (combinedTarget, newDelegate);
        }
        else
          deadRefs.Add (mt);
      }

      foreach (MethodTarget mt in deadRefs)   // Remove dead references
        _targets.Remove (mt);                 // from _targets.

      return combinedTarget as TDelegate;
    }
    set
    {
      _targets.Clear();
      Combine (value);
    }
  }
}
public class Foo
{
  WeakDelegate<EventHandler> _click = new WeakDelegate<EventHandler>();

  public event EventHandler Click
  {
    add { _click.Combine (value); } remove { _click.Remove (value); }
  }

  protected virtual void OnClick (EventArgs e)
  {
    EventHandler target = _click.Target;
    if (target != null) target (this, e);
  }
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription