实施适当的类似 GetHashCode 函数的策略
Strategy to implement a proper GetHashCode-like function
问题
实现给定对象能够 return 哈希键的函数的最佳方法是什么?
要求是:
HashCodeFn(((bool?)false, "example")) != HashCodeFn(((bool?)null, "example"))
- 计算起来相对便宜
- 适用于任何类型,没有任何特定要求(例如
[Serializable]
属性)
我试过的
我试过 .GetHashCode
但是:
- 对于
null
vs 0
vs 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)
的请求并且您想要 "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,您可以减少它,例如,不为每个成员生成哈希码。
而且这段代码显然可以清理干净。这里又快又脏。
问题
实现给定对象能够 return 哈希键的函数的最佳方法是什么?
要求是:
HashCodeFn(((bool?)false, "example")) != HashCodeFn(((bool?)null, "example"))
- 计算起来相对便宜
- 适用于任何类型,没有任何特定要求(例如
[Serializable]
属性)
我试过的
我试过 .GetHashCode
但是:
- 对于
null
vs0
vsfalse
这样的事情是不可靠的
- 需要为每种类型实施
我试过:
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)
的请求并且您想要 "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,您可以减少它,例如,不为每个成员生成哈希码。
而且这段代码显然可以清理干净。这里又快又脏。