反对 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 相等性问题的机制。
我不是数据库专家,所以对于这个选项,其他人可能更了解这个解决方案的好坏。
我想获取任何对象并获得代表该对象的 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 相等性问题的机制。
我不是数据库专家,所以对于这个选项,其他人可能更了解这个解决方案的好坏。