The Problem
Primitive Obsession is one of the most pervasive code smells out there. You can address it by moving a primitive in to a simple class.
Loading Gist 58e39edaba3f6aea753f5ea5d0484c95...
I call the resulting class a "value type", but don't confuse it with C#'s notion of a value type, which doesn't get its own heap allocation, and is passed-by-value to other methods, and is a source of bugs if it's mutable. I mean "a type that represent a value in some domain".If you want to implement equality on that class, there are a lot of tricky details that are easy to get wrong, at least in C#. For example:
Loading Gist 4ae17ec794f49665844e3b3e01614867...
This will throw an exception when trying to cast the Bar to a Point. So you try to fix it:
Loading Gist 939c8c7d570e7a7476659fcb75ae2658...
This will throw when trying to call null.GetType(). Uggh.
You probably want to override operator==() as well.
Loading Gist 0a70ef42a2ec6c24e6bb6425defa058e...
The compiler tells you to implement operator!=() to go with it, so you copy/paste and change the method name:
Loading Gist e6fad485cf86e0b9832f6ebccae2dcad...
Oops, you forgot to negate the check. Bug.
If the value in question is a case-insensitive identifier of some sort, it's important that the GetHashCode() is implemented correctly. Don't do this:
Loading Gist 8d025d68706998bf091445454a2e4c74...
Maybe you want to implement IEquatable<>, too, and you better get these details right there, too.
Many programmers don't test these details at all, or they test a few but not all, and they have to repeat the same set of tests each time they introduce a new class. If you discover a new rule (ToString() should follow equality, right?) you have to update all the tests.
Prior Art
Assertion libraries typically have an equality assertion. For example, in NUnit: Assert.AreEqual( new Point(7,8), new Point(7,8) );
That is insufficient. It only tells you that one of the equality checks you've written is correct, and doesn't catch all the other cases listed above.
The Solution
ValueTypeAssertions addresses all the mistakes I have ever made, or seen made, or can imagine when implementing equality in C#. Grab it from NuGet, and write a unit test like this: ValueTypeAssertions.HasValueEquality(new NtfsPath("foo.txt"), new NtfsPath("foo.txt"));
This says "these two objects should equal, in every way that C# recognizes equality".
ValueTypeAssertions.HasValueInequality(new NtfsPath("foo.txt"), new NtfsPath("bar.txt"));
Which says the same thing about not being equal.
If some part of your value should be case insensitive, just add another assertion:
If some part of your value should be case insensitive, just add another assertion:
ValueTypeAssertions.HasValueEquality(new NtfsPath("foo.txt"), new NtfsPath("FOO.TXT"));
If you wrap two values, assert the combinations:
You can find the source on GitHub.
What change would make it more useful to you?
Is there a name for this that would be more obvious?
ValueTypeAssertions.HasValueInequality(new Point(1, 2), new Point(1, 8));
ValueTypeAssertions.HasValueInequality(new Point(1, 2), new Point(0, 2));
Feedback
Do you find this useful?What change would make it more useful to you?
Is there a name for this that would be more obvious?
No comments:
Post a Comment