
Intransitive "best" practices
Intransitive relationships in math can be denoted by A = B, B = C Δ C may or may not equal A. Moving from one framework to another, or one language to another, may mean some "best practices" are not transitive (that is, they can't be moved from one context to another and be expected to be true).
In the early days of C#, for example, it was assumed that the gospel of the double-checked locking pattern was transitive from C++ to C# (at least by a few people). The double-checked locking pattern is a pattern by which, in order to avoid slow locking operations, a variable is checked for null prior to locking as an optimization. This variable checking is done in order to implement lazy-initialization (for example, in a singleton). For example (as stolen from Jon Skeet, comments mine):
public sealed class Singleton { static Singleton instance = null; static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { if (instance == null) // first check { lock (padlock) { if (instance == null) // double-checked { instance = new Singleton(); } } } return instance; } } }
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
As you can see from the code, it's apparent where the name "double-checked" came from. The assumption with this code is that a lock
is only needed if the one and only initialization of instance
needs to be performed. It's assumed that once it's initialized (and thus, no longer null) there's no need to lock because it never changes again. All fairly logical assumptions, unless we take into account the memory model of .NET (1.x, at the time). Neither the compiler nor the processor were required to perform the previous instructions in the order they were written, and the processor wasn't guaranteed not to be using a cached value (more on the Itanium than the x86, but who knows what processor will be used when we run our code). I won't get into the nitty-gritty details, but suffice it to say, an established "best" practice in C++ was a "worst" practice in C#.
Incidentally, the double-checked locking pattern was never really a good idea in C++. It was proved flawed in 2004 by Scott Meyers and Andrei Alexandrescu for very similar reasons compared to C#. This goes to show how some practices are not only intransitive, but become "worst" practices with further research or knowledge.
In .NET 2.0 (as well as later versions of Java) the memory model was changed to actually make double-checked locking work (at least in a non-debug build in .NET). You could actually write it in such a way as to get it to work in both .NET 1.x and .NET 2.0+, but, by that point the damage was done and double-checked locking had become evil. It certainly wasn't the quickest code anyway, but I digress. If you are interested in more of the details, I'd recommend Jon Skeet's C# In Depth, where he details use of the static initialization feature of C# to implement singletons and avoid the need for double-check locking altogether.