我发现自己陷入了困境,所以我们开始吧。
我需要生成指纹哈希码以进行对象区分。比较两组对象的哈希值,需要告诉我是否存在具有相同哈希值的相同对象。
指纹hash must be platform-independent。所以我去了 MD5哈希。
我正在使用无法控制的大型对象模型代码库。我无法通过此指纹修改的所有类型。我无法添加属性或构造函数或修改任何内容。这不排除类型将来会更改。因此,任何方法都必须是编程的-我不能仅仅创建一个Surrogate类来避免该问题;至少不是手动的。
但是,性能不是问题,因此反射具有完全的绿灯。
此外,我将需要能够控制哈希中属性的排除。如果我排除某个属性,则两个对象的所有属性都相同,除了一个对象仍需要获取相同的哈希值。
Byte[]
MD5哈希要求将对象序列化为Byte []。
序列化要求将该类标记为[Serializable]
。我无法添加到遗留代码中,很自然地添加到can not be added at runtime either中。
所以我去了 protobuf-net
。
Protobuf rightly fails when encountering types that implement an interface with Getter-only auto-properties:
public interface ISomeInterface
{
double Vpy { get; }
double Vy { get; }
double Vpz { get; }
...
}
要通过多种类型实现此接口,使用Surrogates似乎也是不可行的(不切实际,不可维护)。
我只需要序列化而不是反序列化,所以我不明白为什么在这种情况下protobuf-net的局限性。 我知道protobuf-net不能在需要时往返,但我不需要往返!
我真的弯腰了吗? 还有其他选择吗?
就像我说的那样,这非常有效,但前提是对象没有任何类型(或嵌套属性)的类型(该类型具有仅具有Getter的自动属性)。
public static byte[] ToByteArray(this object obj, List<PropertyInfo> exclusionsProps = null)
{
if (exclusionsProps == null)
exclusionsProps = new List<PropertyInfo>();
// Protobuf-net implementation
ProtoBuf.Meta.RuntimeTypeModel model = ProtoBuf.Meta.TypeModel.Create();
AddPropsToModel(model, obj.GetType(), exclusionsProps);
byte[] bytes;
using (var memoryStream = new MemoryStream())
{
model.Serialize(memoryStream, obj);
bytes = memoryStream.GetBuffer();
}
return bytes;
}
public static void AddPropsToModel(ProtoBuf.Meta.RuntimeTypeModel model, Type objType, List<PropertyInfo> exclusionsProps = null)
{
List<PropertyInfo> props = new List<PropertyInfo>();
if (exclusionsProps != null)
props.RemoveAll(pr => exclusionsProps.Exists(t => t.DeclaringType == pr.DeclaringType && t.Name == pr.Name));
props
.Where(prop => prop.PropertyType.IsClass || prop.PropertyType.IsInterface).ToList()
.ForEach(prop =>
{
AddPropsToModel(model, prop.PropertyType, exclusionsProps); //recursive call
}
);
var propsNames = props.Select(p => p.Name).OrderBy(name => name).ToList();
model.Add(objType, true).Add(propsNames.ToArray());
}
然后我将这样使用:
foreach (var obj in objs)
{
byte[] objByte = obj.ToByteArray(exclusionTypes);
using (MD5 md5Hash = MD5.Create())
{
string hash = GetMd5Hash(md5Hash, objByte);
Console.WriteLine(obj.GetType().Name + ": " + hash);
}
}
答案 0 :(得分:2)
这里的简单解决方案是完全避开问题的根本原因。
当您无法修改现有类,但需要对其进行一些修改时,最简单的方法是创建一个新的和改进的子类,在其中您可以进行所需的修改
考虑到传统代码库显然将在您的控件之外进行更改,处理这些更改的唯一方法是在运行时生成这些类型。幸运的是,C#允许您发出可以完全解决此问题的中间语言。
您将从DefineType
method available from the ModuleBuilder
class开始。具体来说,您想使用带有String,TypeAttributes和Type(代表您扩展的类)的重载
答案 1 :(得分:1)
您指出
如果两个对象具有相同的哈希值,则将它们视为彼此的精确副本
请意识到,哈希具有有限的熵,而源对象具有无限的熵。哈希冲突一定会发生。让我们看一些示例:
public class Point
{
public int X;
public int Y;
}
public class Coordinate
{
public int X;
public int Y;
}
假设我们将哈希计算为X ^ Y
。即使两个类的实例代表不同的类,它们也可以具有相同的哈希值。即使仅采用这些类中的一个,如果我们采用一个X = 1,Y = 2而另一个X = 2,Y = 1的实例,则它们具有相同的哈希值。当然,您可以优化哈希算法来减轻发生碰撞的风险,但是您不能确保始终避免这种冲突。
相反,我将实现DeepEquals方法。这需要更多的精力(如果您自己编写)。但是,如果实施正确,它可以确保将两个对象复制。