实现适当的类似于GetHashCode的函数的策略

问题描述 投票:0回答:1

问题

实现给定对象能够返回哈希键的功能的最佳方法是什么?

要求为:

  • HashCodeFn(((bool?)false, "example")) != HashCodeFn(((bool?)null, "example"))
  • 计算起来相对便宜
  • 适用于任何类型,没有任何特殊要求(例如[Serializable]属性)

我尝试过的

我已经尝试过.GetHashCode,但是:

  • [null vs0vs false之类的东西不可靠
  • 每种类型都需要实现

我尝试过:

    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();
    }

但是:

  • 它要求对象树中的所有类型都实现[Serializable](某些类型我无法控制,并且不实现它们)

我正在考虑以最紧凑的形式将对象序列化为JSON,然后获取该字符串的GetHashCode,但是我不确定它与NodaTime.Instant之类的东西配合得如何。这是最快的方法吗?


特定用例

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

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

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

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

问题是实现给定对象能够返回哈希键的功能的最佳方法是什么?要求为:HashCodeFn((((bool?)false,“ example”)))!= HashCodeFn(((bool?)null,“ ...

c# .net-core hash .net-core-3.0 c#-8.0
1个回答
0
投票

我认为没有任何特别有效的方法可以完成您想要的事情。将需要某种额外的处理,以确保您获得适当的哈希码。另外,请记住,如果您无法控制的类已经实现了Equals且GetHashCode和Equals返回true,例如,它们之间的区别仅在于诸如nullable布尔值false或null之类的差异,则GetHashCode返回不同​​的值是不正确的。] >

您可以序列化为JSON以实现所需的功能。这将排除任何可能被注释为排除的字段。假设没有任何与哈希码相关的字段被排除,那么它将起作用。或者,您可以为将导致冲突的类型编写扩展功能,并为这些字段自定义哈希。然后使用反射(也可能在序列化为JSON中使用反射)遍历类成员,并在必要时使用扩展使用哈希码。类似于下面的代码。

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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.