C# 在C中使用BinaryFormatter序列化对同一对象的引用#

C# 在C中使用BinaryFormatter序列化对同一对象的引用#,c#,serialization,C#,Serialization,我正在使用BinaryFormatter序列化一个对象图,当对同一对象有多个引用时,我希望在反序列化后保留该引用,即反序列化数据中的引用仍应引用同一对象 这似乎适用于属于同一反序列化()调用的所有对象,但不适用于同一BinaryFormatter实例上的单独调用。有没有办法对其进行不同的配置,以便在多个调用中正确保留引用 请参见以下示例,其中ReferenceEquals()与来自不同反序列化()调用的对象一起返回false using System; using System.Diagnost

我正在使用
BinaryFormatter
序列化一个对象图,当对同一对象有多个引用时,我希望在反序列化后保留该引用,即反序列化数据中的引用仍应引用同一对象

这似乎适用于属于同一
反序列化()调用的所有对象,但不适用于同一
BinaryFormatter
实例上的单独调用。有没有办法对其进行不同的配置,以便在多个调用中正确保留引用

请参见以下示例,其中
ReferenceEquals()
与来自不同
反序列化()
调用的对象一起返回false

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializeTest
{
  [Serializable]
  class Data
  {
    public int Value { get; set; }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var data = new Data { Value = 1234 };

      using (var fs = new FileStream("serialized.dat", FileMode.Create, FileAccess.Write))
      {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(fs, (data, data));
        formatter.Serialize(fs, data);
      }

      using (var fs = new FileStream("serialized.dat", FileMode.Open, FileAccess.Read))
      {
        BinaryFormatter formatter = new BinaryFormatter();

        var (data1, data2) = ((Data, Data))formatter.Deserialize(fs);
        var data3 = (Data)formatter.Deserialize(fs);

        // object references from separate calls are not the same?
        // accordings to docs, same Formatter and ObjectIdGenerator should lead to the same object being deserialized.
        Debug.Assert(ReferenceEquals(data1, data2), "objects from same call");
        Debug.Assert(ReferenceEquals(data1, data3), "objects from different calls");
      }
    }
  }
}

表面上看,这是不可能的,因为对象引用只对每个反序列化调用有意义。在内部,每个调用都会创建一个二进制文件来保存引用

如果你只想知道它们是否相等,你可以实现平等

[Serializable]
class Data
{
    public int Value { get; set; }

    public override bool Equals(object obj) => 
        (obj is Data data) ? data.Value == Value : false;

    public override int GetHashCode() => Value.GetHashCode();
}
这意味着:

Debug.Assert(Equals(data1, data2), "objects from same call");
Debug.Assert(Equals(data1, data3), "objects from different calls");
如果您绝对需要引用相等,可以通过实现,并记录结果,尝试在多个调用之间共享状态

更新:

好吧,如果让你白费力气去寻找一个可能根本不可行的解决方案,我会很难过。所以我自己试过:

让我们尝试添加一个类型,该类型通过将每个实例映射到一个唯一的id来包装引用等式,该id可以是
GetHashCode
本身

[Serializable]
public class Ref<T>
{
    public readonly T Value;
    public readonly int Id;
    public Ref(T value) { Value = value; Id = value.GetHashCode(); }

    protected class RefSurrogate : ISerializationSurrogate
    {
        Dictionary<int, Ref<T>> Instances = new Dictionary<int, Ref<T>>();

        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var refs = (Ref<T>)obj;
            info.AddValue(nameof(Id), refs.Id);
            if (!Instances.ContainsKey(refs.Id))
            {
                Instances.Add(refs.Id, refs);
                info.AddValue(nameof(Value), refs.Value);
            }
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var id = (int)info.GetInt32(nameof(Id));
            if (Instances.TryGetValue(id, out var refs))
                return refs;
            else
                return Instances[id] = new Ref<T>((T)info.GetValue(nameof(Value), typeof(T)));
        }
    }

    public static SurrogateSelector AddTo(SurrogateSelector ss)
    {
        ss.AddSurrogate(typeof(Ref<T>), new StreamingContext(StreamingContextStates.All), new RefSurrogate());
        return ss;
    }
}
[可序列化]
公共类参考
{
公共只读T值;
公共只读int-Id;
公共引用(T值){value=value;Id=value.GetHashCode();}
受保护的类引用代理:ISerializationSubrogate
{
字典实例=新字典();
public void GetObjectData(对象对象对象、序列化信息、StreamingContext上下文)
{
var refs=(Ref)obj;
info.AddValue(name of(Id),refs.Id);
如果(!Instances.ContainsKey(refs.Id))
{
添加(refs.Id,refs);
信息添加值(名称(值)、参考值);
}
}
公共对象SetObjectData(对象对象对象、序列化信息、StreamingContext上下文、iUrrogateSelector选择器)
{
var id=(int)info.GetInt32(nameof(id));
if(Instances.TryGetValue(id,out var refs))
返回参考文献;
其他的
返回实例[id]=newref((T)info.GetValue(nameof(Value),typeof(T));
}
}
公共静态代理选择程序AddTo(代理选择程序ss)
{
AddSurrogate(typeof(Ref)、newstreamingContext(streamingContextState.All)、new RefSurrogate());
返回ss;
}
}
以及测试程序:

[Serializable]
class Data
{
    public int Value { get; set; } 
}

class Program
{
    static void Main(string[] args)
    {
        var data = new Ref<Data>(new Data { Value = 1234 });

        BinaryFormatter formatter = new BinaryFormatter();

        using (var fs = new FileStream("serialized.dat", FileMode.Create, FileAccess.Write))
        {
            formatter.Serialize(fs, (data, data));
            formatter.Serialize(fs, data);
        }

        using (var fs = new FileStream("serialized.dat", FileMode.Open, FileAccess.Read))
        {
            // Create a SurrogateSelector.

            formatter.SurrogateSelector = Ref<Data>.AddTo(new SurrogateSelector());

            var (data1, data2) = ((Ref<Data>, Ref<Data>))formatter.Deserialize(fs);
            var data3 = (Ref<Data>)formatter.Deserialize(fs);

            // object references from separate calls are not the same?
            // accordings to docs, same Formatter and ObjectIdGenerator should lead to the same object being deserialized.

            Debug.Assert(ReferenceEquals(data1.Value, data2.Value), "objects from same call");
            Debug.Assert(ReferenceEquals(data1.Value, data3.Value), "objects from different calls");
        }
    }
}
[可序列化]
类数据
{
公共int值{get;set;}
}
班级计划
{
静态void Main(字符串[]参数)
{
var数据=新引用(新数据{Value=1234});
BinaryFormatter formatter=新的BinaryFormatter();
使用(var fs=new FileStream(“serialized.dat”、FileMode.Create、FileAccess.Write))
{
序列化(fs,(数据,数据));
序列化(fs,数据);
}
使用(var fs=new FileStream(“serialized.dat”、FileMode.Open、FileAccess.Read))
{
//创建代理选择器。
formatter.SurrogateSelector=Ref.AddTo(new-SurrogateSelector());
var(data1,data2)=((Ref,Ref))格式化程序。反序列化(fs);
var data3=(Ref)格式化程序。反序列化(fs);
//来自不同调用的对象引用不相同?
//根据文档,相同的格式化程序和ObjectedGenerator应该导致反序列化相同的对象。
Assert(ReferenceEquals(data1.Value,data2.Value),“来自同一调用的对象”);
Assert(ReferenceEquals(data1.Value,data3.Value),“来自不同调用的对象”);
}
}
}

从表面上看,这是不可能的,因为对象引用只对每个反序列化调用有意义。在内部,每个调用都会创建一个二进制文件来保存引用

如果你只想知道它们是否相等,你可以实现平等

[Serializable]
class Data
{
    public int Value { get; set; }

    public override bool Equals(object obj) => 
        (obj is Data data) ? data.Value == Value : false;

    public override int GetHashCode() => Value.GetHashCode();
}
这意味着:

Debug.Assert(Equals(data1, data2), "objects from same call");
Debug.Assert(Equals(data1, data3), "objects from different calls");
如果您绝对需要引用相等,可以通过实现,并记录结果,尝试在多个调用之间共享状态

更新:

好吧,如果让你白费力气去寻找一个可能根本不可行的解决方案,我会很难过。所以我自己试过:

让我们尝试添加一个类型,该类型通过将每个实例映射到一个唯一的id来包装引用等式,该id可以是
GetHashCode
本身

[Serializable]
public class Ref<T>
{
    public readonly T Value;
    public readonly int Id;
    public Ref(T value) { Value = value; Id = value.GetHashCode(); }

    protected class RefSurrogate : ISerializationSurrogate
    {
        Dictionary<int, Ref<T>> Instances = new Dictionary<int, Ref<T>>();

        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var refs = (Ref<T>)obj;
            info.AddValue(nameof(Id), refs.Id);
            if (!Instances.ContainsKey(refs.Id))
            {
                Instances.Add(refs.Id, refs);
                info.AddValue(nameof(Value), refs.Value);
            }
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var id = (int)info.GetInt32(nameof(Id));
            if (Instances.TryGetValue(id, out var refs))
                return refs;
            else
                return Instances[id] = new Ref<T>((T)info.GetValue(nameof(Value), typeof(T)));
        }
    }

    public static SurrogateSelector AddTo(SurrogateSelector ss)
    {
        ss.AddSurrogate(typeof(Ref<T>), new StreamingContext(StreamingContextStates.All), new RefSurrogate());
        return ss;
    }
}
[可序列化]
公共类参考
{
公共只读T值;
公共只读int-Id;
公共引用(T值){value=value;Id=value.GetHashCode();}
受保护的类引用代理:ISerializationSubrogate
{
字典实例=新字典();
public void GetObjectData(对象对象对象、序列化信息、StreamingContext上下文)
{
var refs=(Ref)obj;
info.AddValue(name of(Id),refs.Id);
如果(!Instances.ContainsKey(refs.Id))
{
添加(refs.Id,refs);
信息添加值(名称(值)、参考值);
}
}
公共对象SetObjectData(对象对象对象、序列化信息、StreamingContext上下文、iUrrogateSelector选择器)
{
var id=(int)info.GetInt32(nameof(id));
if(Instances.TryGetValue(id,out var refs))
返回参考文献;
其他的
返回实例[id]=newref((T)info.GetValue(nameof(Value),typeof(T));
}
}
公共静态代理选择程序AddTo(代理选择程序ss)
{
艾德索尔