I’ve been a fan of the Effective XXX series, begun by Scott Meyers in 1997 with his "Effective C++." The books in this series contain several dozen tips about your favorite programming language, telling you what to do and what not to do. These books are easy to read and a great source of thought.
Although these books are a reader’s paradise, they are incredibly difficult to write. To understand this, just try writing a "use/don’t use this C# feature" article, or just remember some holy-porn in your collective that started with the innocent phrase, "and let’s use as instead of the type conversion operator everywhere" or some other similar phrase.
The problem with any use/don’t use/avoid advice is the abundance of exceptions that follow any such rule. For example, should we use mutable value types? Any .NET programmer coming from C++ will answer positively (because it is faster!), then he will read about problems and his opinion will probably change to the opposite. After that, a barrier may arise in his mind that he will no longer be able to overcome even when he needs to sacrifice security for efficiency and use structures in his code.
All of this can lead to a "hardening" of thinking and Cargo culture which can result in an entire team avoiding certain features of a programming language, just because someone out there said it was bad.
This is why when reading (or listening to) any advice in the series, to do or not to do something, you should try to understand the reason for that advice. This will allow you to generalize that advice and use it in a broader context, and just as importantly, it will give you an understanding of when it is time to follow that advice and when it is time to break it!
It’s not hard to guess that I wouldn’t be writing about all this if I didn’t have questions for Effective C#. Unfortunately for me, there were more questions than I expected.
One of the key features of the books and articles John Skeet is his rigor in his use of terms and concepts, as well as his precision in describing language constructions. John can sacrifice depth, but there will at least be a footnote about the fact that there are a number of boundary conditions. The author of Effective C# is not as rigorous or consistent in this regard, resulting in bloopers of varying magnitude.
Inaccuracy #1. Redefining static methods
«You never override the static Object.Reference and static Object.Equals() because they provide the correct tests, regardless of the runtime type.»
The author writes several times that it is not necessary to override (override or redefine) static methods because they behave the way they are supposed to. The only problem is that we can’t do that in C# anyway.
Any article or book is a kind of "abstraction" that emphasizes key characteristics while omitting unnecessary details. The difficulty, however, is that this "abstraction" does not involve important aspects.
Inaccuracy #2. Time of life local variables
All reference types, even local variables, are allocated on the heap. Every local variable of a reference type becomes garbage as soon as the function exits.
The garbage collector is more complicated than it may seem and local variables may be reachable for the collector before the method is complete!
Inaccuracy #3 About the operator ==
«No matter what type is involved, a == a is always true.»
In fact, for the .NET platform, this rule is not always enforced. Can you give an example of when this condition is violated?
Inaccuracy #4. Virtuality interfaces
«Members declared in interfaces are not virtual – at least, not by default.
Interface methods are not virtual. When you implement an interface, you are declaring a concrete implementation of a particular contract in that type.»
I agree that interface inheritance has its own characteristics. Yes, any implementation of an interface method will implicitly be "sealed", but that’s a characteristic of the method that implements the interface method, not the interface itself.
Inaccuracy #5. Order creation objects
«Here is the order of operations for constructing the first instance of a type:
- Static variable storage is set to 0.
- Static variable initializers execute.
- Static constructors for the base class execute.
- The static constructor executes.
Now we know that the order of calling static constructors is not that simple In fact, calling the static constructor of the heir does not lead to calling the static constructor of the base class, and when creating an instance of the heir, the static constructor of even the type being created may not be called.
But if the previous inaccuracies can be attributed to my letter-writing, there are a number of more serious points.
Inaccuracy #6. About collection iterators
In tip 21, the author gives an excellent example of using closed inner classes to implement collection iterators. As a proof of this approach, the following quote is given :
«The .NET Framework designers followed the same pattern with the other collection classes: Dictionary<T> contains a private DictionaryEnumerator<T> , Queue<T> contains a QueueEnumerator<T> , and so on. The enumerator class being private gives many advantages…»
There are two problems here: first, the collection enumerators are structures, and second, these structures are open. Iterators are a known source of incomprehensible behavior, because iterators are inherently modifiable, and modifiable structures are a very dangerous business.
I would understand if this example were hypothetical (because for my own collections this example is not so bad). But in a book with the title "Effective C#" such liberties and mistakes seem quite strange to me.
Inaccuracy #7. About Equals and GetHashCode
There are a lot of questions about the Equals and GetHashCode methods ( Inaccuracy #3 also from this area, by the way, my answer to the question is: it’s Double.NaN).
You can break a leg with all these Equals and GetHashCode, but firstly, the author devoted more than one section to this topic, and secondly, that’s why we read advanced books, to find answers to such questions.
So, for example, the author writes the following about the need to override the GetHashCode method:
«For reference types, it works but is inefficient. For value types, the base class version is often incorrect »
The inefficiency of the Object.GetHashCode method from the author’s point of view is explained as follows. The GetHashCode value for objects uses an internal counter, which increases as each object is created. Since the counter value is not random, the resulting hash code will lead to frequent collisions and low search efficiency.
Simple experiments show that CLR behaves slightly differently (calling (new object()).GetHashCode() twice results in quite different values). A quick googling proves that the implementation is somewhat more complicated.
The second part of the statement is even stranger. In fact, even implementation of GetHashCode method returning 42 is correct. Yes, inefficient but absolutely correct. This time the author insists that the ValueType.GetHashCode method implementation simply returns the hash code of the first field. This is almost true, and the default implementation can return the transformed hash code of the first field. Yes, this implementation may cause more collisions, but you can’t call it incorrect.
In fact, if we get into such implementation details, it’s better to describe them in more detail, explaining not only the current behavior, but also the reasons for such implementation.
Is it really that bad?
Whether a book is useful depends greatly on your experience and, most importantly, your attitude toward the material you are reading. If you have plenty of experience, you simply won’t find anything new. If you take other people’s advice with a healthy pragmatism and have not read Skeet Or de Smet. , then this book can be a good source for discussing the different features of the C# language.
If things are so ambiguous, what does this book do A list of classic C#/.NET books ? I honestly admit that my opinion was based on the first edition of this book I read about 4-5 years ago. Would I have added it if I were making this list today? I’m not sure! Nevertheless, it is a fairly unique book in its own right, albeit with a less than perfect execution. So until there is a real replacement on the market, I will not remove it from this list.
Rating : 3