I am using a neat little HashCode
utility with some instance methods that help quickly generate Hash values. I've simplified it for this question, but it's basically this:
public struct HashCode
{
private readonly int _hashCode;
public HashCode(int hashCode) { _hashCode = hashCode; }
override int GetHashCode() => _hashCode;
public static HashCode Start => new HashCode(17);
public HashCode Hash(int integer) => new HashCode(unchecked(integer + hashCode * 31));
public HashCode Hash<T>(T value) where T : struct => Hash(value.GetHashCode());
public HashCode Hash(string value) => Hash(value?.GetHashCode() ?? 17);
public HashCode Hash<T>(T? nullable) where T : struct =>
Hash(nullable?.GetHashCode() ?? 17);
// Other reference types, user must explicitly specify a comparer
public HashCode Hash<T>(T obj, IEqualityComparer<T> comparer) =>
Hash(obj == null ? 17: comparer.GetHashCode(obj));
public static implicit operator int(HashCode hashCode) => hashCode.GetHashCode();
}
Allowing for slick hash implementations like:
class Person {
// ...
override GetHashCode() => HashCode.Start.Hash(name).Hash(age).Hash(...);
}
As you can see, it goes out of its way to avoid boxing, etc. If you're hashing a reference type directly, you must specify a comparer to make sure you know how it's being hashed. Seems reasonable
Now, I was hoping to add some extension methods in one of my projects (i.e. without duplicating & modifying the library) so that I could easily add consice hash functions some of my own frequently used types, and employ them using the exact same syntax:
public static class HashCode_Extensions
{
public static HashCode Hash(this HashCode hc, DateRange range) =>
hc.Hash(range?.begin).Hash(range?.end);
public static HashCode Hash(this HashCode hc, IEnumerable<T> list) where T : struct =>
list.Aggregate(hc, (hc, elem) => hc.Hash(elem));
// etc...
}
I thought I was so clever and tons of copy-pasted code was going to disappear.
class Meeting {
// ...
override GetHashCode() => HashCode.Start.Hash(name).Hash(dateRange).Hash(invitees);
}
Unfortunately, the compiler is choosing the generic instance method over my own, even though my method is a perfect fit and the generic method has a type constraint that is not
CS0453 The type 'DateRange' must be a non-nullable value type in order to use it as parameter 'T' in the generic type of method 'HashCode.Hash(T)'
Obviously the compiler has decided that the "best overload" of Hash
is the one with the non-satisfied generic type constraint. Too bad, because my extension method would have been a perfect fit.
Is there any way I can trick the compiler into using the correct method without having to resort to using a different function name or include 'dummy' arguments in my call to Hash
?
I'd also be happy if there is some backwards-compatible way for me to change the HashCode
library that would allow my extension methods to be noticed. (I'd rather not add my custom overloads to the base library, as those custom types don't currently exist in its namespace)