如何序列化可能只有唯一实例的类
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所示。
【讨论】:
以上是关于如何序列化可能只有唯一实例的类的主要内容,如果未能解决你的问题,请参考以下文章