反对 GUID/UUID

Object to GUID/UUID

我想获取任何对象并获得代表该对象的 GUID。

我知道这涉及很多事情。我正在为常见应用程序寻找足够好的解决方案。

我的具体用例是用于缓存,我想知道用于创建我正在缓存的东西的对象过去已经创建了一个。将有 2 种不同类型的对象。每个类型仅包含 public 个属性,并且可能包含一个 list/ienumable.

假设对象可以序列化,我的第一个想法是将它序列化为 json(通过本机 jsonserlizer 或 newtonsoft),然后获取 json 字符串并将其转换为一个 uuid 版本 5,详见这里的要点 How can I generate a GUID for a string?

如果它不可序列化(例如包含字典),我的第二种方法是在 public 属性上使用反射来生成某种类型的唯一字符串,然后将其转换为 uuid 版本 5。

这两种方法都使用 uuid 版本 5 将字符串获取到 guid。是否有经过验证的 c# class 可以生成有效的 uuid 5 guid?要点看起来不错,但想要确定。

我正在考虑将 c# 命名空间和类型名称设为 uuid 5 的命名空间。这是对命名空间的有效使用吗?

我的第一种方法对于我的简单用例来说已经足够好了,但我想探索第二种方法,因为它更灵活。

如果创建 guid 不能保证合理的唯一性,它应该抛出一个错误。超级复杂的对象肯定会失败。如果使用反射,我怎么知道是这种情况?

我正在寻找新的方法或 concerns/implementations 第二种方法。


编辑:近 3 年后我 bounty/reopened 的原因是因为我再次需要它(并且再次用于缓存);也是因为在 c# 7.3 中引入了通用非托管约束。 http://devblogs.microsoft.com/premier-developer/dissecting-new-generics-constraints-in-c-7-3/ 上的博客 post 似乎建议如果对象可以遵守非托管规范,您可以为键值存储找到合适的键。我误会了什么吗?

这仍然是有限的,因为对象(泛型)必须遵守非常有限的非托管类型约束(没有字符串、没有数组等),但它更近了一步。我不完全理解为什么获取内存流和获取 sha1 哈希的方法不能在非托管类型上完成。

我知道引用类型指向内存中的位置,要获得代表所有对象的内存并不容易;但感觉可行。毕竟,对象最终是由一堆非托管类型的实现组成的(字符串是数组字符等)

PS: GUID 要求宽松,512 位或以下的 integer/string 就足够了

正如其他人在评论中所说,如果您愿意接受 int 作为密钥,听起来 GetHashCode 可能对您有用。如果不是,则有一个 Guid 构造函数,它采用 byte[] 长度为 16。您可以尝试类似以下的操作

using System.Linq;
class Foo
{
    public int A { get; set; }
    public char B { get; set; }
    public string C { get; set; }
    public Guid GetGuid()
    {
        byte[] aBytes = BitConverter.GetBytes(A);
        byte[] bBytes = BitConverter.GetBytes(B);
        byte[] cBytes = BitConverter.GetBytes(C);
        byte[] padding = new byte[16];
        byte[] allBytes =
            aBytes
                .Concat(bBytes)
                .Concat(cBytes)
                .Concat(padding)
                .Take(16)
                .ToArray();
        return new Guid(allBytes);
    }
}

正如评论中所说,这里没有完全由银制成的子弹,但有一些非常接近。使用哪一个取决于你想要使用 class 的类型和你的上下文,例如你什么时候认为两个对象是平等的。但是,请注意您将始终面临可能的冲突,单个 GUID 不足以保证避免冲突。你能做的就是降低碰撞的概率。

在你的情况下,

already made one in the past

听起来您不想引用引用相等性,而是想使用值相等性的概念。最简单的方法是相信 classes 使用值相等来实现相等,因为在这种情况下,您已经使用 GetHashCode 完成了,但冲突的可能性更高,因为它只是32位。此外,你会假设写 class 的人做得很好,这并不总是一个好的假设,特别是因为人们倾向于责怪你而不是他们自己。

否则,最好的机会是将序列化与您选择的哈希算法相结合。我会推荐 MD5,因为它是最快的并且可以生成 GUID 所需的 128 位。如果你说你的类型只包含 public 属性,我建议使用 XmlSerializer 像这样:

    private MD5 _md5 = new MD5CryptoServiceProvider();
    private Dictionary<Type, XmlSerializer> _serializers = new Dictionary<Type, XmlSerializer>();
    public Guid CreateID(object obj)
    {
      if (obj == null) return Guid.Empty;
      var type = obj.GetType();
      if (!_serializers.TryGetValue(type, out var serializer))
      {
        serializer = new XmlSerializer(type);
        _serializers.Add(type, serializer);
      }
      using (var stream = new MemoryStream())
      {
         serializer.Serialize(stream, obj);
         stream.Position = 0;
         return new Guid(_md5.ComputeHash(stream));
      }
    }

几乎所有序列化程序都有其缺点。 XmlSerializer 无法序列化循环对象图,DataContractSerializer 要求您的类型具有专用属性,并且基于 SerializableAttribute 的旧序列化程序需要设置该属性。你必须以某种方式做出假设。

平等问题是个难题。
这里有一些关于如何解决您的问题的想法。

哈希序列化对象
一种方法是序列化一个对象,然后按照 Georg 的建议对结果进行哈希处理。
使用 md5 校验和可为您提供正确输入的强校验和。
但是做对了就是问题了。

您可能无法使用通用序列化框架,因为:

  • 他们不关心浮点数是 1.0 还是 1.000000000000001。
  • 他们对什么是平等的理解可能与您/您的雇主不同。
  • 他们用不需要的符号使序列化文本膨胀。 (性能)
  • 序列化文本中的一点偏差会导致散列文本中的大偏差 GUID/UUID。

因此,您应该仔细测试您所做的任何序列化。
否则你可能会得到 false possitives/negatives 对象(主要是漏报)。

思考的几点:

  • 花车和双打:
    始终以相同的方式编写它们,最好使用相同的位数,以防止 1.000000000000001 与 1.0 之类的东西相互干扰。
  • 日期时间、时间戳等:
    应用不会更改且明确的固定格式。
  • 无序集合:
    在序列化之前对数据进行排序。顺序必须明确
  • 字符串:
    相等是否区分大小写?如果不是,则将所有字符串设为小写或大写。
    如有必要,使它们具有文化不变性。
  • 更多:
    对于每种类型,请仔细考虑什么是平等的,什么不是。特别考虑边缘情况。 (float.NaN、-0 对 0、空等)

使用现有的序列化器还是自己做,完全由您决定。
自己做比较麻烦,而且容易出错,但是你可以完全控制相等和序列化的所有方面。
使用现有的序列化器也容易出错,因为您需要测试或证明结果是否总是如您所愿。


引入明确的顺序并使用树
如果对源码有控制权,可以引入自定义下单功能。
该顺序必须考虑所有属性、子对象、列表等。 然后就可以创建一个二叉树,并使用顺序插入和查找对象。

第一种方法提到的相同问题仍然存在,您需要确保检测到相等的值。 大 O 性能也比使用散列差。但是在大多数真实的实例中,实际性能应该是相当的或者至少足够快。

好处是,一旦发现 属性 或值不相等,您就可以停止比较两个对象。因此无需总是查看整个对象。 二叉树需要 O(log2(n)) 次比较才能查找,因此速度会非常快。

不好的是,您需要访问所有实际对象,从而将它们保存在内存中。 哈希表只需要 O(1) 次查找比较,因此甚至会更快(至少理论上)。


将它们放入数据库
如果您将所有对象存储在数据库中,则数据库可以为您进行查找。
数据库非常擅长比较对象,它们内置了处理 equality/near 相等性问题的机制。

我不是数据库专家,所以对于这个选项,其他人可能更了解这个解决方案的好坏。