为什么在完全反序列化对象之前调用Equals方法?

时间:2019-07-16 13:44:53

标签: c# dictionary serialization

我想序列化和反序列化包含在字典中的对象,实现Equals方法并在其中包含字典。但是,它会导致ArgumentException,因为在反序列化时调用Equals方法为时过早。

using System.Collections.Generic;
using System.Runtime.Serialization;
using NUnit.Framework;

[Serializable]
public class SerializableObject
{
    public Dictionary<string, bool> Values = new Dictionary<string, bool>();

    public override bool Equals(object obj)
    {
        if (!(obj is SerializableObject other))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (Values.Count != other.Values.Count)
            return false;
        foreach (var (k, v) in Values)
            if (!other.Values.TryGetValue(k, out var otherV) || v != otherV)
                return false;
        return true;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

[Test]
public void DeserializeDictionary()
{
    using (var stream = new MemoryStream())
    {
        var obj1 = new SerializableObject();
        obj1.Values["aaa"] = true;
        var obj2 = new SerializableObject();
        obj2.Values["bbb"] = false;

        var formatter = new BinaryFormatter();

        var dict = new Dictionary<SerializableObject, bool>
        {
            [obj1] = true,
            [obj2] = false
        };

        formatter.Serialize(stream, dict);
        stream.Seek(0, SeekOrigin.Begin);
        dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);

        Assert.Contains(obj1, dict.Keys);
        Assert.Contains(obj2, dict.Keys);
    }
}

反序列化时,在两个具有空字典的对象上调用Equals方法,并且发生ArgumentException(“已经添加了具有相同键的项”)。

此外,如果对象实现了[OnDeserialized]方法,则在字典仍然为空且内部字典在稍后返回整个顶级字典之前填充时,也会调用此方法。

编辑:在这个简短的示例中,GetHashCode故意返回0以强制调用Equals。在实际程序中,只有两个不等的对象具有相同的哈希码(冲突)。

编辑2 :这是一个嵌套对象的更复杂的示例。

[Serializable]
public class SerializableObject : ISerializable
{
    public SerializableObject() {}

    public Dictionary<string, bool> Values = new Dictionary<string, bool>();

    public override bool Equals(object obj)
    {
        if (!(obj is SerializableObject other))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (Values.Count != other.Values.Count)
            return false;
        foreach (var kvp in Values)
            if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
                return false;
        return true;
    }

    public override int GetHashCode() => 0;

    public void GetObjectData(SerializationInfo info, StreamingContext context) =>
        info.AddValue(nameof(Values), Values.ToArray());

    private SerializableObject(SerializationInfo info, StreamingContext context) =>
        Values = new Dictionary<string, bool>(info.Get<KeyValuePair<string, bool>[]>(nameof(Values)));
}

[Serializable]
public class SerializableObject2 : ISerializable
{
    public SerializableObject2() { }

    public Dictionary<SerializableObject, bool> Values = new Dictionary<SerializableObject, bool>();

    public override bool Equals(object obj)
    {
        if (!(obj is SerializableObject2 other))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (Values.Count != other.Values.Count)
            return false;
        foreach (var kvp in Values)
            if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
                return false;
        return true;
    }

    public override int GetHashCode() => 0;

    public void GetObjectData(SerializationInfo info, StreamingContext context) =>
        info.AddValue(nameof(Values), Values.ToArray());

    private SerializableObject2(SerializationInfo info, StreamingContext context) =>
        Values = new Dictionary<SerializableObject, bool>(info.Get<KeyValuePair<SerializableObject, bool>[]>(nameof(Values)));
}

[Test]
public void DeserializeDictionary()
{
    using (var stream = new MemoryStream())
    {
        var obj1 = new SerializableObject();
        obj1.Values["aaa"] = true;
        var obj2 = new SerializableObject();
        obj2.Values["bbb"] = false;

        var sobj1 = new SerializableObject2
        {
            Values = new Dictionary<SerializableObject, bool>
            {
                [obj1] = true,
                [obj2] = false
            }
        };

        var formatter = new BinaryFormatter();

        formatter.Serialize(stream, sobj1);
        stream.Seek(0, SeekOrigin.Begin);
        sobj1 = (SerializableObject2)formatter.Deserialize(stream);

        Assert.Contains(obj1, sobj1.Values.Keys);
        Assert.Contains(obj2, sobj1.Values.Keys);
    }
}

2 个答案:

答案 0 :(得分:2)

我不确定,只是在推测,但是直到SerializableObject被序列化(在本例中为Dictionary之后,dict似乎才被填充。 )添加了它们。

似乎您可以通过SerializableObject实现ISerializable并提供自己的GetObjectData和反序列化构造函数来解决此问题。

[Serializable]
public class SerializableObject : ISerializable
{
  public SerializableObject()
  {
  }

  public Dictionary<string, bool> Values = new Dictionary<string, bool>();

  public override bool Equals(object obj)
  {
     if (!(obj is SerializableObject other))
        return false;
     if (ReferenceEquals(this, obj))
        return true;
     if (Values.Count != other.Values.Count)
        return false;
     foreach (var kvp in Values)
        if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
           return false;
     return true;
  }

  public override int GetHashCode()
  {
     return 0;
  }

  public void GetObjectData(SerializationInfo info, StreamingContext context)
  {
     info.AddValue(KeyValuePairsKey, Values.Select(kvp => kvp).ToArray());
  }

  private SerializableObject(SerializationInfo info, StreamingContext context)
  {
     var kvps = info.GetValue(KeyValuePairsKey, typeof(KeyValuePair<string, bool>[])) as KeyValuePair<string, bool>[];
     foreach (var kvp in kvps)
     {
        Values.Add(kvp.Key, kvp.Value);
     }
  }

  private const string KeyValuePairsKey = "KVPS";
}

class Program
{
  static void Main(string[] args)
  {
     using (var stream = new MemoryStream())
     {

        var obj1 = new SerializableObject();
        obj1.Values["aaa"] = true;
        var obj2 = new SerializableObject();
        obj2.Values["bbb"] = false;

        var formatter = new BinaryFormatter();

        var dict = new Dictionary<SerializableObject, bool>
        {
           [obj1] = true,
           [obj2] = false
        };

        formatter.Serialize(stream, dict);
        stream.Seek(0, SeekOrigin.Begin);
        dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);
     }
  }
}

答案 1 :(得分:0)

您总是在GetHashCode()方法中返回0。我认为字典使用GetHashCode()来确定键的相等性。