Building on our implementation of entity equality, we are now in a position to implement value equality. This is more complex because it tends to have a greater number of factors to consider.
Simple Value Types
If your value type contains just a single field (as we’ve already seen in many of the examples so far in this series) then the implementation can be fairly simple.
Consider the TimeSeriesId
that we discussed previously:
Equality is defined entirely on the basis of the sole private field, the _id
wrapped by the type. As long as we keep the implementations of .Equals()
and .GetHashCode()
consistent with each other, we’re good to go.
Complex Value Types
For value types that contain multiple fields, things get just a smidgen more complicated. Consider the information needed to record a specific observation from a time series:
Note this example is somewhat simplified to make it easy to discuss - actual time series observations can, in some contexts, be significantly more involved.
-
TimeSeries
is the unique identifier of the series to which this observation belongs - notice the nested semantic type. -
Subject
is the unique identifier of the party to which this observation applies. This might be an individual, an incorporated company, a non-incorporated organization, or a defined group of any of these. -
Observed
is the date to which the observation applies. Note that we’re not usingDateTimeOffset
from the framework - we don’t want to have to deal with time. -
Measure
is the measurement of the series, captured as a decimal to avoid the rounding errors associated with floats and doubles.
It’s worth pausing to notice what we’re not including here. We’re assuming all measurements are simple numeric values, though this isn’t actually true for all series (e.g. credit ratings). Also, there is no allowance for observations to be revised, something we’d have to do in any real-world system.
As in prior discussions, we start by implementing the interface IEquatable<T>
:
After supporting an easy shortcut, we’re careful here to test all of our fields. One of the more common bugs I’ve seen when reviewing implementations of value equality is the sin of omission - leaving a field out of the equality test.
We can then override .Equals()
in the usual way:
The implementation of .GetHashCode()
has a few interesting quirks:
We combine together the existing implementations of .GetHashCode()
, using an XOR operation.
To try and minimize the chances of different types cancelling each other out, we multiply each hash code by a different prime number. I feel that the exact prime numbers used aren’t particularly important, though disagree with me fervently. If you don’t like the idea of using small primes as shown here, choose a different three-digit prime for each field.
The calculation is done inside a unchecked block so that any integer overflow that happens doesn’t take the program down.
Comments
blog comments powered by Disqus