实施适当的类似 GetHashCode 函数的策略

Strategy to implement a proper GetHashCode-like function

问题

实现给定对象能够 return 哈希键的函数的最佳方法是什么?

要求是:


我试过的

我试过 .GetHashCode 但是:

我试过:

    private static int GetHashKey<T>(T input)
    {
        using var memoryStream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(memoryStream, input);
        memoryStream.Position = 0;
        using var reader = new StreamReader(memoryStream);
        return reader.ReadToEnd().GetHashCode();
    }

但是:

我正在考虑以最紧凑的形式将对象序列化为 JSON,然后获取该字符串的 GetHashCode,但我不确定它与类似NodaTime.Instant。这是完成此任务的最快方法吗?


具体用例

如果这对理解用例有任何帮助,这将用作数据加载器密钥(参见 github.com/graphql/dataloader 示例)。

特别是数据加载器密钥用于处理批处理。当您有许多输入 (a, b, c) 的请求并且您想要 "pivot" 时,例如 a (这意味着 (1, b, c), (2, b, c), (3, b, c) 应该调用批处理函数 fn([1, 2, 3], (b, c))那么您需要能够为 (b, c) 的相同值定义一个相同的键,以用作数据加载器键。

从输入的角度来看,例如,在 b 之类的东西上指定或不指定 bool 被认为是 2 个不同的东西,应该在两个不同的函数上进行批处理。

如果我要使用 (b, c).GetHashCode() 那么我会认为 ((bool?)false, "ok")((bool?)null, "ok") 是同一件事,因此将它们批处理到同一个批处理函数会产生意想不到的结果。

我不认为有任何特别有效的方法来做你想做的事。将需要进行某种额外处理以确保您获得适当的哈希码。另外,请记住,如果您不控制的 classes 已经实现了 Equals 和 GetHashCode 和 Equals returns true,例如它们的区别仅在于可空布尔值是 false 或 null,那么GetHashCode return 不同的值是不正确的。

你可以序列化到 JSON 来实现你想要的。这将排除任何可能恰好被注释为排除的字段。假设 none 与哈希码相关的字段被排除在外,那将起作用。或者,您可以为将导致冲突的类型编写扩展函数,并为这些字段自定义散列。然后使用反射(也可能用于 JSON 的序列化)迭代 class 成员并在必要时使用您的扩展获取哈希码。类似于下面的代码。

class ThingToHash
{
    public bool? CouldBeFalseOrNullOrNull { get; }
    public int IncludesZero { get; }
    public string CanBeEmptyOrNull { get; }
    private string Hidden { get; }

    public ThingToHash(bool? couldBeFalseOrNull, int includesZero, string canBeEmptyOrNull)
    {
        CouldBeFalseOrNullOrNull = couldBeFalseOrNull;
        IncludesZero = includesZero;
        CanBeEmptyOrNull = canBeEmptyOrNull;
    }
}

static class StringExtensions
{
    public static int GetAltHashCode(this string toHash)
    {
        return toHash?.GetHashCode() ?? 17;
    }
}

static class NullableBoolExtensions
{
    public static int GetAltHashCode(this bool? toHash)
    {
        return toHash?.GetAltHashCode() ?? true.GetHashCode() * 19;
    }
}

static class BoolExtensions
{
    public static int GetAltHashCode(this bool toHash)
    {
        if (false == toHash)
        {
            return true.GetHashCode() * 17;
        }

        return toHash.GetHashCode();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(false.GetHashCode());
        Console.WriteLine(((bool?)null).GetHashCode());
        Console.WriteLine(false == (bool?)null);

        Console.WriteLine(HashUnknownObject(new ThingToHash(null, 0, "")));
        Console.WriteLine(HashUnknownObject(new ThingToHash(false, 0, "")));

        Console.ReadKey();
    }

    static int HashUnknownObject(Object toHash)
    {
        PropertyInfo[] members = toHash.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        int hash = 17;

        foreach (PropertyInfo memberToHash in members)
        {
            object memberVal = memberToHash.GetValue(toHash);

            if (null == memberVal)
            {
                if (typeof(bool?) == memberToHash.PropertyType)
                {
                    hash += 31 * ((bool?)null).GetAltHashCode();
                }
                else if (typeof(string) == memberToHash.PropertyType)
                {
                    hash += 31 * ((string)null).GetAltHashCode();
                }
            }
            else
            {
                hash += 31 * memberToHash.GetValue(toHash).GetHashCode();
            }
        }

        return hash;
    }
}

您显然必须添加其他检查才能使用 bool 扩展,添加其他扩展等以涵盖您需要的情况。并进行测试以检查使用反射序列化的影响。对于已经实现 GetHashCode 的 classes,您可以减少它,例如,不为每个成员生成哈希码。

而且这段代码显然可以清理干净。这里又快又脏。