Chapter 12 - Disposal and Garbage Collection

Current Memory Stats

void Main()
{
  long totalMemAllocated = 0;
  for (int i = 0; i < 200; i++)
  {
    totalMemAllocated += AllocateSomeNonreferencedMemory();
    string procName = Process.GetCurrentProcess().ProcessName;
    using PerformanceCounter pcPB = new PerformanceCounter ("Process", "Private Bytes", procName);
    long memoryUsed = GC.GetTotalMemory (false); // Change to true to force a collection before reporting used memory
    Console.WriteLine ($"Currently OS allocated: {pcPB.NextValue()}. Current GC reported {memoryUsed}. Allocated at some point {totalMemAllocated}.");
  }
}

long AllocateSomeNonreferencedMemory()
{
  int loops = 64;
  int size = 1024;
  for (int i = 0; i < loops; i++)
  {
    int[] array = new int [size];
  }
  
  return loops * size * 4; // int is 32-bits (4 bytes)
}

Anonymous Disposal - Problem

void Main()
{
  var foo = new Foo();
  
  // Test it without calling SuspendEvents()
  foo.FireSomeEvent(); 
  
  // Now test it with event suspension:
  using (foo.SuspendEvents())
  {
    foo.FireSomeEvent();
  }

  // Now test it again without calling SuspendEvents()
  foo.FireSomeEvent();

}

class Foo
{
  int _suspendCount;

  public IDisposable SuspendEvents()
  {
    _suspendCount++;
    return new SuspendToken (this);
  }

  public void FireSomeEvent()
  {
    if (_suspendCount == 0)
      "Event would fire".Dump();
    else
      "Event suppressed".Dump();
  }

  class SuspendToken : IDisposable
  {
    Foo _foo;
    public SuspendToken (Foo foo) => _foo = foo;
    public void Dispose()
    {
      if (_foo != null) _foo._suspendCount--;
      _foo = null;
    }
  }
}

Anonymous Disposal - Solution

void Main()
{
  var foo = new Foo();
  
  // Test it without calling SuspendEvents()
  foo.FireSomeEvent(); 
  
  // Now test it with event suspension:
  using (foo.SuspendEvents())
  {
    foo.FireSomeEvent();
  }

  // Now test it again without calling SuspendEvents()
  foo.FireSomeEvent();

}

class Foo
{
  int _suspendCount;

  public IDisposable SuspendEvents()
  {
    _suspendCount++;
    return Disposable.Create (() => _suspendCount--);
  }

  public void FireSomeEvent()
  {
    if (_suspendCount == 0) 
      "Event would fire".Dump();
    else
      "Event suppressed".Dump();
  }
}

// Reusable class
public class Disposable : IDisposable
{
  public static Disposable Create (Action onDispose)
    => new Disposable (onDispose);

  Action _onDispose;
  Disposable (Action onDispose) => _onDispose = onDispose;

  public void Dispose()
  {
    _onDispose?.Invoke();
    _onDispose = null;
  }
}

Calling Dispose from a finalizer

void Main()
{  
}

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

string filename = "tempref.tmp";

void Main()
{
  // Create and open file so it cannot be deleted
  var writer = File.CreateText(filename);
  
  // Get the temporary reference in a separate method.
  // Variable will go out of scope upon return and be eligible for GC.
  CreateTempFileRef();
  
  GC.Collect(); // Run the garbage collector
  
  TempFileRef._failedDeletions.Dump();
}

void CreateTempFileRef()
{
  var tempRef = new TempFileRef(filename); 
  
}

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

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

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

  // int _deleteAttempt; // Uncomment if re-registering the finalizer
  
  ~TempFileRef()
  {
    try { File.Delete (FilePath); }
    catch (Exception ex)
    {
      DeletionError = ex;
      _failedDeletions.Enqueue (this);   // Resurrection
      // We can re-register for finalization by uncommenting:
      // if (_deleteAttempt++ < 3) GC.ReRegisterForFinalize (this);
    }
  }
}

Array Pooling

int[] pooledArray = ArrayPool<int>.Shared.Rent (100);  // 100 bytes

ArrayPool<int>.Shared.Return (pooledArray);

Managed Memory Leaks

void Main()
{
  CreateClients();
}

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 ... 
}

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
{
  
}

Timers - System.Timer

void Main()
{
  
}

class Foo : IDisposable
{
  System.Timers.Timer _timer;

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

  void tmr_Elapsed (object sender, ElapsedEventArgs e) {  }
  
  public void Dispose() { _timer.Dispose(); }
}

Timers - Threading timer

static void Main()
{
  using (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"); }

Weak References

void Main()
{
  AddWidgets(); // In another method so they go out of scope
  
  Console.WriteLine("Before GC:");
  Widget.ListAllWidgets();
  
  GC.Collect();

  Console.WriteLine ("After GC:");
  Widget.ListAllWidgets();
}

void AddWidgets()
{
  new Widget ("foo");
  new Widget ("bar");
}

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 Delegate

void Main()
{  
}

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)
    => _click.Target?.Invoke (this, e);
}


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

    public MethodTarget (Delegate d)
    {
      // d.Target will be null for static method targets:
      if (d.Target != null) 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 =>
        Equals (d.Target, w.Reference?.Target) &&
        Equals (d.Method.MethodHandle, w.Method.MethodHandle));

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

  public TDelegate Target
  {
    get
    {
      Delegate combinedTarget = null;

      foreach (MethodTarget mt in _targets.ToArray())
      {
        WeakReference wr = mt.Reference;

        // Static target || alive instance target
        if (wr == null || wr.Target != null)
        {
          var newDelegate = Delegate.CreateDelegate (
            typeof (TDelegate), wr?.Target, mt.Method);
          combinedTarget = Delegate.Combine (combinedTarget, newDelegate);
        }
        else
          _targets.Remove (mt);
      }

      return combinedTarget as TDelegate;
    }
    set
    {
      _targets.Clear();
      Combine (value);
    }
  }
}
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