我有一个只能包含如下唯一实例的类。这使得引用相等足以等同于对象。
虽然序列化很简单,但是反序列化有点棘手,因为不能像下面我所做的那样分配给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();
}
答案 0 :(得分: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类型,例如: / p>
public class RootObject
{
// Technique taken from https://stackoverflow.com/questions/3280362/most-elegant-xml-serialization-of-color-structure
[XmlElement(Type=typeof(MyClassDTO))]
public MyClass MyClass { get; set; }
}
.Net小提琴示例在此处显示了此操作: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
机制,该机制类似于数据合约代理替换如Customized Serialization所示,该机制受支持。
SerializationInfo.SetType()
与IObjectReference
结合使用。