【问题标题】:Object to GUID/UUID对象到 GUID/UUID
【发布时间】:2016-09-08 06:19:36
【问题描述】:

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

我知道这需要很多东西。我正在为常见应用寻找足够好的解决方案。

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

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

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

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

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

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

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

我正在寻找第二种方法的新方法或关注点/实施。


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

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

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

PS: GUID 的要求很宽松,任何512位或以下的整数/字符串都可以

【问题讨论】:

  • 你的意思是所谓的hash。例如,您可以使用 MD5。
  • 你不是在找GetHashCode吗? msdn.microsoft.com/en-us/library/…
  • @tomas 哈希码不能保证是唯一的,只是希望没有冲突。它们也特定于该对象类型
  • @ParoX 好吧,谈论 any object 并为任何对象获取 unique id - 任何对象都是 unlimited 一堆对象,您将用一个限制为 16 个字节的 guid 表示无冲突的对象,因此具有 limited 唯一值(按设计)。你看到核心问题了吗?
  • 这听起来像是一个 XY 问题meta.stackexchange.com/questions/66377/what-is-the-xy-problem 例如,您将如何区分new object()new object()?除了散列整个 XML 之外,您将如何在两个大的 XmlDocument 之间产生差异(性能任何人?)?没有适用于任何对象的灵丹妙药。注意 GetHashCode 不是“用于哈希表”,而是用于(不)相等比较。如果两个对象 GetHashCode() 不同,则对象不同(反之不成立)。确实不能解决你的问题。

标签: c# object uuid guid


【解决方案1】:

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

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

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

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

这就是为什么,您应该仔细测试您所做的任何序列化。
否则,您可能会得到对象的误报/误报(主要是误报)。

需要考虑的几点:

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

您是使用现有的序列化程序还是自己做,这取决于您。
自己做更多的工作和更容易出错,但你可以完全控制平等和序列化的所有方面。
使用现有的序列化程序也容易出错,因为您需要测试或证明结果是否总是如您所愿。


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

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

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

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


将它们放入数据库
如果您将所有对象存储在数据库中,那么数据库可以为您进行查找。
数据库在比较对象方面非常出色,并且它们内置了处理相等/近似相等问题的机制。

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

【讨论】:

    【解决方案2】:

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

    在你的情况下,

    以前做过一个

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

    否则,您最好的机会是将序列化与您选择的散列算法相结合。我会推荐 MD5,因为它是最快的,并且可以生成 GUID 所需的 128 位。如果您说您的类型仅包含公共属性,我建议您使用 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 的旧序列化程序需要设置该属性。您必须以某种方式做出假设。

    【讨论】:

      【解决方案3】:

      正如其他人在 cmets 中所说,如果您愿意接受 int 作为您的密钥,听起来 GetHashCode 可能会为您解决问题。如果没有,则有一个 Guid 构造函数,它采用长度为 16 的 byte[]。您可以尝试以下类似的方法

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

      【讨论】:

      • 这是这个类特有的。我想通过反射获取字节可能是一个问题。哈希码也不能保证是唯一的,它不是全局的。 A类哈希码可能与B类相同
      猜你喜欢
      • 1970-01-01
      • 2010-09-11
      • 1970-01-01
      • 2011-02-23
      • 2021-06-20
      相关资源
      最近更新 更多