4 ms·
This is one of the things .net did pretty terribly wrongly for it includes equals and GetHashCode on every object. Not only is that autocompletion (and api) po
by emn13 10y ago
This is one of the things .net did pretty terribly wrongly for it includes equals and GetHashCode on every object.
Not only is that autocompletion (and api) pollution of the absolutely worst kind (infects every "thing" in the language!) it also lends itself to bugs; so most people simply don't implement Equals or GetHashCode at all, or only with tools. If you do, it's easy to get wrong; and the built-in api has no guiderails to help avoid trivial mistakes (why is plain equality by composition so hard to express?). Furthermore, the default choice of reference equality means that in particular reference types need to have some GC-surviving notion of identity. And that means that every reference type has a larger object header: and memory density matters hugely to performance. It's quite conceivable to imagine a system with no object header at all, or more conservatively, only with a (potentially optional) type id.
The built-in api further makes it natural to implement non-symmetric or non-reflexive "equality", and it binds the definition of equality tightly to the type, when in fact (as you point out with IEqualityComparer) equality can easily be perspective dependent.
Edit: Probably by virtue of professional brain damage through years of getting used to the status quo I almost overlooked another bit of insanity: The fact that Equals is not equivalent to "operator ==" ! That simply makes no sense whatsoever, you just have to get used to it. To be clear: it's fine (in rare but necessary niche cases) to have multiple equality relationships; it's a nasty gotcha to spring that on the reader without warning. And to add insult to injury the operators == and != are barely related - there's little help in ensuring they're mutually consistent.
Fortunately, .net has value types, and those by default implement sane equality rules, right? Well... structs work in some sense, but it's not always desirable to couple something with such specific GC and performance characteristics to a semantic, so it's not a trivial switch. And then - the implementation by default of struct only "works" in some academic sense of the word. Sure, it correctly determines equality by composition, but it doesn't implement all the api's (operators and generic interfaces) so it's not always practical, and the performance is consistently very bad (reflection!), and somethings surprisingly terrible: in particular, the hashcode computation ignores some fields and can even ignore all fields due to what can only be described as the worst hashcode algorithm in common use. Even when the GetHashCode does use all fields it's very slow, and it mixes bits so poorly that common data causes unnecessarily many hash collisions. Frankly: struct's equality is a trap, and it's not broadly applicable anyhow.
All in all, to implement something that should be trivial (say: A is equal to B if and only if all components of A are equal to the respective components of B) correctly in a broadly applicable way you'd need to implement not just one method, but five: object.Equals, object.GetHashCode, IEqualityComparer<T>.Equals, operator ==, and operator !=. Just don't make any typos when copy-pasting all that boilerplate, because testing these for correctness isn't easy, and it is easy to end up in a situation that is appears superficially correct but has bugs in corner cases.
So, I'd call equality and related features in .net pretty terribly designed. It looks to me like they didn't think this through and simply copied java 1.0 a little too closely. In fact, I challenge you to name a language that's worse in this regard.
- hvidgaard 10y agoThe only thing .net could have done differently was to make Equals and GetHashcode into interfaces and perhaps included some best practices. == is there for legacy reasons, and meaning reference equality with the ability to overload it, seems like a reasonable compromise for object types. What we're missing is the ability to create custom value types, that consist of other value types. We already know that it's working with F#, so the CLR does support it. Question is if C# the language can be extented with it.
- emn13 10y ago.net could have made equality entirely optional; and it could have exposed machinery to implement equality (including all aspects of it!) by composition, not manual fiddling - at least not by default! That would have made .net less memory-hungry, faster, less bugprone, and simpler. There's absolutely no good reason for this horribly complicated and inefficient state of affairs; not all platforms/languages suffer from these limitations. As you point out, even F# manages to do better despite the considerable extra limitation of needing to maintain some sort of compatibility even at runtime with the current crazy state, and despite what is certainly a much more limited development budget.
- hvidgaard 10y agoIn hindsight there is a lot they should have done differently. Immuteable by default and not nullable by default are two of the obvious. But they where targeting Java developers, so they couldn't just bring a completely different language to the market. They had to be a better Java.
- alkonaut 10y agoYes Equals and GetHashCode should have been in an interface. After 13years of full time .NET it's the only thing I repeat over and over to newer devs: don't override equals and hash code . Use explicit comparers in your dictionaries because equality is rarely property of your THING, it's a property of the use case. I'd prefer the noise of having to always pass an explicit comparer to a Dict constructor, just for clarity. A similar but related thing they got wrong was the possibility to format strings without an explicit formatter. The result of this? Run all unit test suites for all C# projects on a French/Swedish machine (decimal comma) and I can guarantee that most that involve any kind of formatting will fail because of it. I'd be delighted if they changed this behavior, breaking compatibility with every single C# program ever written, that's how bad it is.