我有几个类,一旦设置了初始值,它们就是不可变的。 Eric Lippert 称之为“一次写入不变性”。 在 C# 中实现一次写入不变性通常意味着通过构造函数设置初始值。这些值初始化只读字段。
但是,如果您需要使用 XmlSerializer 或 DataContractSerializer 将这样的类序列化为 XML,则必须有一个无参数构造函数。
有人对如何解决这个问题有建议吗?是否有其他形式的不变性更适合序列化?
编辑:正如@Todd指出的,DataContractSerializer不需要无参数构造函数。根据 MSDN 上的
DataContractSerializer 文档 [DataContract()]
public class Immutable
{
[DataMember(IsRequired=true)]
public readonly string Member;
public Immutable(string member)
{
Member = member;
}
}
public class Immutable
{
public Immutable(string foo, int bar)
{
this.Foo = foo;
this.Bar = bar;
}
public string Foo { get; private set; }
public int Bar { get; private set; }
}
您可以创建一个虚拟类来在序列化/反序列化期间表示该不可变对象:
public class DummyImmutable
{
public DummyImmutable(Immutable i)
{
this.Foo = i.Foo;
this.Bar = i.Bar;
}
public string Foo { get; set; }
public int Bar { get; set; }
public Immutable GetImmutable()
{
return new Immutable(this.Foo, this.Bar);
}
}
当你有一个 Immutable 类型的属性时,不要序列化它,而是序列化一个 DummyImmutable :
[XmlIgnore]
public Immutable SomeProperty { get; set; }
[XmlElement("SomeProperty")]
public DummyImmutable SomePropertyXml
{
get { return new DummyImmutable(this.SomeProperty); }
set { this.SomeProperty = value != null ? value.GetImmutable() : null; }
}
好吧,对于看起来如此简单的东西来说有点长......但它应该可以工作;)
Person p = new Person();
p.Name = "Fred";
p.DateOfBirth = DateTime.Today;
p.Freeze(); // **now** immutable (edit attempts throw an exception)
(或与对象初始值设定项相同)
这非常适合
DataContractSerializer
,只要您处理序列化回调来执行
Freeze
。 XmlSerializer
不进行序列化回调,因此需要更多工作。 不过,如果您使用自定义序列化(
IXmlSerializable
),则两者都适合。同样,自定义序列化在 realio-trulio 不变性下“广泛”可行,但很痛苦 - 而且这有点谎言,因为它是“创建一次,然后调用接口方法” - 所以并不是真正的“正确”不可变。
为了真正的不变性,请使用 DTO。我建议查看这里的讨论:为什么 XML-Serialized 类需要无参数构造函数。
您应该考虑使用 DataContractSerializer。据我从文档中可以看出,这不需要无参数构造函数,并且可以序列化/反序列化私有成员。
我刚刚看了您链接的文章。在他的术语中,使用在构造函数中初始化的只读字段的对象被称为“只写不变性”。
“冰棒不变性”有点不同。 Lippert 给出了两个有用的例子:反序列化(您试图解决的问题)和循环引用,其中 A 和 B 需要独立创建,但 A 需要引用 B,B 需要引用 A .这里已经提到了实现此结果的两种更明显的方法(事实上,正如我正在写这篇文章一样)。 Thomas Levesque 提到的模式基本上是“Builder”模式。但在这种情况下它相当笨重,因为您不仅需要从 Builder 到 Immutable,还需要从 Immutable 到 Builder,以便序列化/反序列化是对称的。
我自己做了一些研究,因为坦率地说,我不太喜欢代理属性或只读字段之类的解决方法。
有一个名为
Extended XML Serializer 的 NuGet 库,它支持序列化不可变类。它确实写得很好并且可扩展,但我不喜欢它的是它依赖于在 XML 中保存反射信息(例如命名空间和类名),这可能会带来安全风险(您可以仔细制作 XML以便实例化非预期的类)。
为了解决这个问题我写了自己的XML序列化库,它支持序列化不可变的模型,但是它仅仅依赖于模型结构,并且不在XML中存储任何代码相关的信息(用法与您可以在 NuGet 存储库中找到
Spooksoft.Xml.Serialization 库。它相对简单且轻量级,但开箱即用,支持集合(List<T>
IReadOnlyList<T>
、
T[]
)和字典(目前Dictionary<TKey,TValue>
)、不同类型、二进制属性等。手册位于 图书馆存储库页面
。