Why no Reference Counting + Garbage Collection in C#?

Brad Abrams posted an e-mail from Brian Harry written during development of the .Net framework. It details many of the reasons reference counting was not used, even when one of the early priorities was to keep semantic equivalence with VB6, which uses reference counting. It looks into possibilities such as having some types ref counted and not others (IRefCounted!), or having specific instances ref counted, and why none of these solutions were deemed acceptable.

Because [the issue of resource management and deterministic finalization] is such a sensitive topic I am going to try to be as precise and complete in my explanation as I can. I apologize for the length of the mail. The first 90% of this mail is trying to convince you that the problem really is hard. In that last part, I'll talk about things we are trying to do but you need the first part to understand why we are looking at these options.

...

We initially started with the assumption that the solution would take the form of automatic ref counting (so the programmer couldn't forget) plus some other stuff to detect and handle cycles automatically. ...we ultimately concluded that this was not going to work in the general case.

...

In summary:

  • We feel that it is very important to solve the cycle problem without forcing programmers to understand, track down and design around these complex data structure problems.
  • We want to make sure we have a high performance (both speed and working set) system and our analysis shows that using reference counting for every single object in the system will not allow us to achieve this goal.
  • For a variety of reasons, including composition and casting issues, there is no simple transparent solution to having just those objects that need it be ref counted.
  • We chose not to select a solution that provides deterministic finalization for a single language/context because it inhibits interop with other languages and causes bifurcation of class libraries by creating language specific versions.

The garbage collector does not require you to write a Dispose method for every class/type that you define. You only define one when you need to explicitly do something to cleanup ; when you have explicitly allocated native resources. Most of the time, the GC just reclaims memory even if you only do something like new() up an object.

The GC does reference counting - however it does it in a different way by finding which objects are 'reachable' (Ref Count > 0) every time it does a collection... it just doesn't do it in a integer counter way. . Unreachable objects are collected (Ref Count = 0). This way the runtime doesn't have to do housekeeping/updating tables everytime an object is assigned or released... should be faster.

The only major difference between C++ (deterministic) and C# (non-deterministic) is when the object would be cleaned up. You can't predict the exact moment an object would be collected in C#.

Umpteenth plug: I'd recommend reading Jeffrey Richter's standup chapter on the GC in CLR via C# in case you're really interested in how the GC works.