【发布时间】:2019-11-25 06:38:15
【问题描述】:
我想序列化和反序列化包含在字典中的对象,实现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);
}
}
【问题讨论】:
-
我知道它总会被调用。问题是当它被调用时,对象还没有被构造,所以 Values 字典是 EMPTY。所以不相等的对象被认为是相等的,因为它们的内容还没有被反序列化!
-
由于哈希码不能保证稳定,字典不能将它们存储在序列化流中。字典的键被反序列化后,字典需要重新计算哈希值。为此,它实现了
IDeserializationCallback,它在对象图反序列化后调用。我不确定是否有任何记录的顺序,当对象图中存在多个此类对象时应该调用IDeserializationCallback,但 current implementation 按 FIFO 顺序组合它们。
标签: c# dictionary serialization