Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

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.



Agree with everything here. I posted an example [1] of how this can be solved now with C# (although in reality it just adds another 'equality solution' to the mix). It uses ad-hoc polymorphism to implement a type-class (interface) and instance (struct) approach, similar to Haskell. There's no IEqualityComparer instance allocation cost either.

[1] https://qht.co/item?id=12954790


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


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


You don't have much say in Equals and GetHashCode when using objects for for instance ItemSource for a ComboBox. But it's not that bad, you just need to have a senior dev sign off the implementations.


A Control that does comparisons isn't much different from any other class that does internal comparisons- just like for a Set or Dictionary. If objects didn't have a default Equals/GetHashCode then all constructors for Dictionaries (And combo boxes) would take an IEqualityComparer argument.


It does complicate things quite a bit because WPF declares things in XAML, and giving constructor arguments is a major pain. You could make it a DependencyProperty, but it's still convoluted.


I'm sure it's already possible and you could make the call default implicit so if you don't pass an equality comparer it just uses DefaultParer.Instance which does what the current default (object) comparer does.


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


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




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: