对象指纹识别:序列化+不可触摸的旧代码+仅限Getter的自动属性=弯角?

时间:2019-07-19 16:08:05

标签: serialization hash protobuf-net automatic-properties fingerprinting

我发现自己陷入了困境,所以我们开始吧。

上下文

我需要生成指纹哈希码以进行对象区分。比较两组对象的哈希值,需要告诉我是否存在具有相同哈希值的相同对象。

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

2 个答案:

答案 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方法。这需要更多的精力(如果您自己编写)。但是,如果实施正确,它可以确保将两个对象复制。