如何序列化可能只有唯一实例的类

Posted

技术标签:

【中文标题】如何序列化可能只有唯一实例的类【英文标题】:How to serialize a class that may only have unique instances 【发布时间】:2018-07-22 16:32:31 【问题描述】:

我有一个类只能有如下的唯一实例。这使得引用相等足以等同于对象。

虽然序列化很简单,但反序列化有点棘手,因为不能像我在下面所做的那样分配给this。如果仍然只想维护类的唯一实例,我该如何反序列化对象?

public sealed class MyClass :
    ISerializable,
    IXmlSerializable

    private static readonly Dictionary<string, MyClass> Cache;

    static MyClass()  /* build cache, use private constructor */ 

    private MyClass(string name)
    
        this.Name = name;
    

    public string Name  get; 

    public static MyClass Parse(string from)
        => Cache[from];

    public void GetObjectData(SerializationInfo info, StreamingContext context)
        => throw new NotImplementedException();

    public XmlSchema GetSchema() => null;

    public void ReadXml(XmlReader reader)
    
        reader.ReadStartElement();
        this = Parse(reader.ReadContentAsString());
        reader.ReadEndElement();
    

    public MyClass(SerializationInfo info, StreamingContext context) 
        => this = Parse(info.GetString(nameof(this.Name)));

    public void WriteXml(XmlWriter writer)
        => throw new NotImplementedException();

【问题讨论】:

如果您能够更改您的课程代码,我强烈建议您将您的课程替换为三个不同的课程;一个用于序列化/反序列化,一个用于缓存,另一个用于您需要缓存的类。您的班级在这里承担不同的职责,这使其更加复杂。 【参考方案1】:

一般来说,处理这个问题的方法是,在对象图序列化过程中,使用serialization surrogate mechanism 替换单例,其中单例替换为Data Transfer Object (DTO),它只包含标识符对于单身人士。然后,稍后,随着图形被反序列化,DTO 最初被反序列化,然后通过查找替换为相应的单例。

例如,如果您的 MyClass 如下所示:

public sealed class MyClass

    private static readonly Dictionary<string, MyClass> Cache;

    static MyClass()
    
        Cache = new Dictionary<string, MyClass>()
        
             "one", new MyClass("one")  OtherRuntimeData = "other runtime data 1"  ,
             "two", new MyClass("two")  OtherRuntimeData = "other runtime data 2"  ,
        ;
    

    // XmlSerializer required parameterless constructor.
    private MyClass() => throw new NotImplementedException();

    private MyClass(string name) => this.Name = name;

    public string Name  get; 

    public string OtherRuntimeData  get; set; 

    public static MyClass Parse(string from) => Cache[from];

    public static IEnumerable<MyClass> Instances => Cache.Values;

然后一个只包含 Name 的 DTO 看起来像:

public sealed class MyClassDTO

    public string Name  get; set; 

    public static implicit operator MyClassDTO(MyClass obj) => obj == null ? null : new MyClassDTO  Name = obj.Name ;

    public static implicit operator MyClass(MyClassDTO dto) => dto == null ? null : MyClass.Parse(dto.Name);

注意implicit operator 用于在 DTO 和原始文件之间进行转换?这样可以更轻松地将代理项注入序列化图中。

不幸的是,在所有常用的 .Net 序列化程序中,没有标准的方法来实现序列化代理 DTO 的注入。每个都有自己独立的机制(或根本没有机制)。分解它们:

数据协定序列化程序支持通过IDataContractSurrogate 接口使用代理项进行替换,如示例所示在this answer 到DataContractJsonSerializer - share an object instance for the whole graph? 或this answer 到How to serialize / deserialize immutable list type in c#。

数据协定序列化程序还支持IObjectReference 接口,如this answer 到C# DataContract Serialization, how to deserialize to already existing instance 所示。

但不幸的是,XmlSerializer 无法方便地支持通过代理机制注入 DTO。最接近的一个是从this answer 到Most elegant XML serialization of Color structure 的技巧,它涉及将XmlElementAttribute.Type 设置为引用您的单例类型的每个属性的DTO 类型,例如如下:

public class RootObject

    // Technique taken from https://***.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
    [XmlElement(Type=typeof(MyClassDTO))]
    public MyClass MyClass  get; set; 
 

.Net fiddle 示例在这里展示了这一点:https://dotnetfiddle.net/R07NiW。请注意,这只适用于我们之前定义的单例及其 DTO 之间的隐式运算符。

通过 Json.NET 进行序列化时,可以将单例替换为 custom JsonConverter 中的 DTO。

protobuf.net 支持通过RuntimeTypeModel.Default.Add(typeof(OriginalType), false) .SetSurrogate(typeof(SurrogateType)) 替换为代理项,如Can I serialize arbitrary types with protobuf-net?、No serializer defined for type: System.Windows.Media.Media3D.Point3D 或文章Protobuf-net: the unofficial manual 所示。

最后,如果您使用BinaryFormatter(我不推荐使用here 解释的原因),则支持使用类似于数据合约代理替换机制的SurrogateSelector 机制,如@中所示987654343@。

还支持SerializationInfo.SetType()IObjectReference组合,如this documentation example所示。

【讨论】:

以上是关于如何序列化可能只有唯一实例的类的主要内容,如果未能解决你的问题,请参考以下文章

headfirst x

当单例模式遇到序列化会出现什么问题

单例模式--反射--防止序列化破坏单例模式

从 appdomain 中解包不可序列化的类

JUC并发编程 --单例模式

如何在 Django 中序列化模型实例?