Object to GUID/UUID
本文关键字:UUID GUID to Object | 更新日期: 2023-09-27 18:06:19
我想获取任何对象并获得代表该对象的guid
我知道这需要做很多事情。我正在寻找一个足够好的解决方案,为常见的应用程序。
我的特定用例是用于缓存,我想知道用于创建我正在缓存的东西的对象在过去已经创建了一个。有两种不同类型的物体。每种类型只包含公共属性,并且可以包含一个list/ienumable。
假设对象可以序列化我的第一个想法是序列化它到json(通过本机jsonserlizer或newtonsoft),然后采取json字符串,并将其转换为uuid版本5在这里的gist中详细说明我如何生成一个GUID的字符串?
如果它是不可序列化的(例如包含字典),我的第二种方法是在公共属性上使用反射来生成某种类型的唯一字符串,然后将其转换为uuid版本5。
两种方法都使用uuid版本5来接受一个字符串作为guid。是否有一个经过验证的c#类可以生成有效的uuid ?要点看起来不错,但我想确定一下。
我正在考虑使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位的整数/字符串都可以
平等问题是一个困难的问题。
这里有一些关于如何解决这个问题的想法。
对序列化对象进行散列
一种方法是序列化一个对象,然后按照Georg的建议对结果进行散列。
使用md5校验和为您提供具有正确输入的强校验和。
但问题是怎么做才对。
您可能在使用通用序列化框架时遇到困难,因为:
- 他们不关心浮点数是1.0还是1.000000000000001。他们可能和你/你的雇主对什么是平等有不同的理解。
- 他们用不必要的符号使序列化文本膨胀。(性能)
- 序列化文本中的一点偏差会导致散列GUID/UUID的大偏差。
这就是为什么您应该仔细测试您所做的任何序列化。
否则你可能会得到对象的假阳性/假阴性(大多数是假阴性)。
思考要点:
- 漂浮,双打:
总是用相同的方式写它们,最好是用相同的位数,以防止像1.000000000000001和1.0这样的东西干扰。 - 日期时间、时间戳等:
应用一个固定的格式,不会改变,并且是明确的。 - 无序集合:
在序列化数据之前对其进行排序。顺序必须是明确的 字符串 - :
相等是否区分大小写?
如有必要,将它们设置为区域性不变。 - :
对于每一种类型,仔细思考什么是平等的,什么是不平等的。特别是考虑边缘情况。(浮动。NaN, -0 vs 0, null等)
是使用现有的序列化器还是自己做,这取决于您。
自己做会更费力,也更容易出错,但是你可以完全控制等式和序列化的所有方面。使用现有的序列化器也很容易出错,因为您需要测试或证明结果是否总是像您想要的那样。
引入明确的顺序并使用树
如果您可以控制源代码,您可以引入自定义订单函数。
该顺序必须考虑到所有属性、子对象、列表等。然后,您可以创建一个二叉树,并使用该顺序插入和查找对象。
与第一种方法提到的相同的问题仍然存在,您需要确保检测到相等的值。大0性能也比使用哈希更差。但在大多数实际示例中,实际性能应该是相当的,或者至少足够快。
好处是,一旦发现两个对象的属性或值不相等,就可以停止比较。因此不需要总是看整个对象。对于一个查找,二叉树需要O(log2(n))次比较,因此会非常快。
不好的是,您需要访问所有实际对象,因此将它们保存在内存中。对于一个查找,哈希表只需要O(1)次比较,因此甚至会更快(至少理论上)。
将它们放入数据库
如果您将所有对象存储在数据库中,那么数据库可以为您查找。
数据库在比较对象方面相当好,并且它们有内置的机制来处理相等/近似相等问题。
我不是数据库专家,所以对于这个选项,其他人可能更了解这个解决方案有多好。
正如其他人在评论中所说,如果您愿意接受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);
}
}
正如评论中所说,这里没有完全由银制成的子弹,但有一些非常接近。使用哪一个取决于你想要使用你的类的类型和你的上下文,例如,你什么时候认为两个对象是相等的。但是,请注意,您将始终面临可能的冲突,单个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
的旧序列化器也要求设置该属性。你总得做些假设