【问题标题】:Deterministic UUID / hash from an object or its properties in .NET C#.NET C# 中对象或其属性的确定性 UUID / 哈希
【发布时间】:2021-07-04 20:15:34
【问题描述】:

我需要一种方法,在给定对象或对象的属性子集的情况下,跨 .NET 框架/环境/执行以最小的冲突一致地生成唯一 ID。

  • 反复使用相同的属性应该总是产生相同的结果
  • 冲突应该是最小的
  • 属性是字符串、整数和小数的混合体
  • 属性值不是敏感数据
  • 不会添加或删除属性
  • 使用它的应用程序只是内部的(不向公众开放)
  • 我不想依赖 GetHashCode,它可能因 .NET 框架/环境/执行而异,如 great article at andrewlock.net 中所述
    • 他建议使用possible method,但没有提供太多关于实际使用的信息

例如,给定一个固定对象

public class ExampleObject
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public int Prop3 { get; set; }
    public decimal Prop4 { get; set; }
}

我正在考虑将所有值转换为字符串,将它们连接起来,然后使用一些确定性算法来获得所需的结果

// Convert all values to strings and concat
string str1 = "a";
string str2 = "b";
int int1 = 1;
decimal dec1 = 1.2m;
string concatStr = $"{str1}_{str2}_{int1.ToString()}_{dec1.ToString()}";

// Use the string to generate the desired value

我从字符串中看到了人们generate a MD5 hash,但我不确定字符串/整数/小数的数百万种可能组合在现实世界中的用法。 字符串的小数点也让我担心。该值不应该改变,但.ToString() 可以在不同的环境中以不同的方式处理精度吗?

【问题讨论】:

  • 这不是 GUID/UUID。进行加密哈希,然后将结果截断为您需要的位数。 SHA-1 可能已经足够好了(现在应该避免数字签名,但这不是你正在做的)。这就是 GIT 使用的。字符串化任何你想要的,使用编码(比如,UTF-8)将结果字符串转换为字节数组,然后使用 System.Security.Cryptography 中的 SHA-1
  • @madreflection 好点!甚至没有想到这一点。你的意思是提交 sha1 哈希?快速搜索,看起来很相似,它们只是将字符串连接在一起并生成它:gist.github.com/masak/2415865
  • 您可能希望在字符串化描述中包含类名(甚至可能是属性名)。您甚至可以进行 JSON 序列化来获取属性名称和值
  • @Flydog57 感谢您的建议。是否包含属性名称 + 值或只是将整个对象序列化为 JSON 以包含对象的定义,以防它发生变化?

标签: c# .net hash


【解决方案1】:

这将为您提供对象、类名、属性名和属性值的完整签名。我做一个 JSON 序列化,在类名前面加上,然后我散列整个混乱。

首先,这里是我计算哈希的地方:

public static byte[] GetObjectHash<T>(T obj)
{
    var serialized = JsonConvert.SerializeObject(obj);
    var stringified = typeof(T).Name + serialized;
    var bytes = Encoding.UTF8.GetBytes(stringified);
    using (var sha1 = SHA1.Create()) {
        var result = sha1.ComputeHash(bytes);
        return result;
    }
}

我有一个帮助函数将哈希转换为字符串(有更好的实现,搜索此站点以获得更好的实现(如:How do you convert a byte array to a hexadecimal string, and vice versa?))。

public static string BytesToHex(byte[] bytes)
{
    var buffer = new StringBuilder(bytes.Length * 2);
    foreach (var byt in bytes)
    {
        buffer.Append(byt.ToString("X2"));
    }

    return buffer.ToString();
}

然后我对其进行测试(使用您的课程(使用我更喜欢的名称)):

var obj1 = new HashExampleClass
{
    Prop1 = "hello",
    Prop2 = "world",
    Prop3 = 42,
    Prop4 = 123.45m,
};

var obj2 = new HashExampleClass
{
    Prop1 = "hello",
    Prop2 = "world",
    Prop3 = 42,
    Prop4 = 123.45m,
};

var obj3 = new HashExampleClass
{
    Prop1 = "hello",
    Prop2 = "world",
    Prop3 = 41,
    Prop4 = 123.45m,
};

var hash1 = GetObjectHash(obj1);
var hash2 = GetObjectHash(obj2);
var hash3 = GetObjectHash(obj3);

Debug.WriteLine(BytesToHex(hash1));
Debug.WriteLine(BytesToHex(hash2));
Debug.WriteLine(BytesToHex(hash3));

这个输出看起来像:

7190AA1C6ED17E3F302AB61BB5C639320FBA0C00
7190AA1C6ED17E3F302AB61BB5C639320FBA0C00
0B73706DAF7AC39AB0E00CC781AE0FA238A0FC2C

请注意,前两个散列用于不同的对象,但具有完全相同的属性值(因此它们的散列匹配)。最后一个有一个不同的属性值。

如果你在 main 函数中设置断点,你会看到 stringified 变量最终变成一个字符串,如下所示:

HashExampleClass{"Prop1":"hello","Prop2":"world","Prop3":42,"Prop4":123.45}

(如果你查看调试器,你会看到很多 "\"" 转义引号,但字符串确实是我显示的)

【讨论】:

    猜你喜欢
    • 2020-06-09
    • 2021-01-22
    • 1970-01-01
    • 2021-11-07
    • 2017-12-14
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2011-01-07
    相关资源
    最近更新 更多