35

.NET Standard 2.1 / .NET Core 3 introduce System.HashCode to quickly combine fields and values to a hash code without having to care about the underlying implementation.

However, it only provides Combine method overloads for up to 8 values. What do I do if I have a class with 9 values (3x3 matrix) or even 16 values (4x4 matrix)?

Should I simply add together the results of two Combine calls, passing as many values as possible in each?

public override int GetHashCode()
    => HashCode.Combine(M11, M12, M13, M21, M22, M23, M31, M32) + HashCode.Combine(M33);

Looking at the source, I cannot completely argue if this may have implications I don't know of.

2 Answers 2

68

As stated in the System.HashCode documentation, adding together hashes returned by successive HashCode.Combine calls is NOT the solution.

While the static HashCode.Combine method overloads only allow up to 8 values, these are just convenience methods - to combine more, instantiate the HashCode class itself and use it as follows:

public override int GetHashCode()
{
    HashCode hash = new();
    hash.Add(M11);
    hash.Add(M12);
    hash.Add(M13);
    hash.Add(M21);
    hash.Add(M22);
    hash.Add(M23);
    hash.Add(M31);
    hash.Add(M32);
    hash.Add(M33);
    return hash.ToHashCode();
}

It does make me wonder why there is no HashCode constructor accepting a params object[] values so you could do all that in one line, but there are probably reasons I didn't think of this quickly. (S. the comments why such an overload does not exist.)

Sign up to request clarification or add additional context in comments.

6 Comments

It might have something to do with allocating an array, which can be a performance drag. Since .NET Core is more performance oriented, I would think that that's the reason.
Not only array allocation but also boxing of every value type. Horribly inefficient.
It neither does boxing nor array allocation
@Vlad Why not? Surely it has to do with performance considerations..
@Vlad I think the concerns about boxing and array allocation were made about a constructor accepting a params object[] values which absence I mentioned in my answer, not the calls to HashCode.Add.
|
3

If you happen to be able to use C# 13 (.NET 9), and all your types are the same, you can write a simple generic helper method that avoids boxing as well as the array allocation.

This example uses C# 14 in order to make it a static extension method off HashCode, but if you can only use C# 13, you can just make this a static method in a helper class.

extension(HashCode)
{
    public static int Create<T>(params ReadOnlySpan<T> values)
    {
        var hashCode = new HashCode();
        foreach (var value in values)
        {
            hashCode.Add(value);
        }

        return hashCode.ToHashCode();
    }
}

Usage:

HashCode.Create(M11, M12, M13, M21, M22, M23, M31, M32, M33);

Behind the scenes, the compiler will make use of InlineArray in order to avoid the allocation.

Of note, if this is absolutely performance critical, it will not beat @Ray's answer for speed. But we're only talking on the order of single-digit ns slower.

1 Comment

Thanks for the update, wish I could accept multiple answers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.