不变性和 XML 序列化

问题描述 投票:0回答:6

我有几个类,一旦设置了初始值,它们就是不可变的。 Eric Lippert 称之为“一次写入不变性”。 在 C# 中实现一次写入不变性通常意味着通过构造函数设置初始值。这些值初始化只读字段。

但是,如果您需要使用 XmlSerializer 或 DataContractSerializer 将这样的类序列化为 XML,则必须有一个无参数构造函数。

有人对如何解决这个问题有建议吗?是否有其他形式的不变性更适合序列化?

编辑:正如@Todd指出的,DataContractSerializer不需要无参数构造函数。根据 MSDN 上的

DataContractSerializer 文档

,DataContractSerializer“不会调用目标对象的构造函数。”

c# xml-serialization immutability
6个回答
13
投票

[DataContract()] public class Immutable { [DataMember(IsRequired=true)] public readonly string Member; public Immutable(string member) { Member = member; } }



12
投票

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; } }

好吧,对于看起来如此简单的东西来说有点长......但它应该可以工作;)


10
投票

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。据我从文档中可以看出,这不需要无参数构造函数,并且可以序列化/反序列化私有成员。

2
投票

我刚刚看了您链接的文章。在他的术语中,使用在构造函数中初始化的只读字段的对象被称为“只写不变性”。

“冰棒不变性”有点不同。 Lippert 给出了两个有用的例子:反序列化(您试图解决的问题)和循环引用,其中 A 和 B 需要独立创建,但 A 需要引用 B,B 需要引用 A .

这里已经提到了实现此结果的两种更明显的方法(事实上,正如我正在写这篇文章一样)。 Thomas Levesque 提到的模式基本上是“Builder”模式。但在这种情况下它相当笨重,因为您不仅需要从 Builder 到 Immutable,还需要从 Immutable 到 Builder,以便序列化/反序列化是对称的。

1
投票
因此,Marc Gravell 提到的“锁定”模式似乎更有用。虽然我不熟悉 C#,所以我不确定如何最好地实现它。我猜可能是私有财产,例如“锁定”。然后,所有 getter 方法都需要显式检查对象是否已锁定(也称为“冻结”)。如果是这样,他们应该抛出异常(这是一个运行时错误;你不能在编译时强制 popstick 不变性)。

我自己做了一些研究,因为坦率地说,我不太喜欢代理属性或只读字段之类的解决方法。

有一个名为

Extended XML Serializer 的 NuGet 库,它支持序列化不可变类。它确实写得很好并且可扩展,但我不喜欢它的是它依赖于在 XML 中保存反射信息(例如命名空间和类名),这可能会带来安全风险(您可以仔细制作 XML以便实例化非预期的类)。

为了解决这个问题我写了自己的XML序列化库,它支持序列化不可变的模型,但是它仅仅依赖于模型结构,并且不在XML中存储任何代码相关的信息(用法与

0
投票
类似) .

您可以在 NuGet 存储库中找到

Spooksoft.Xml.Serialization 库。它相对简单且轻量级,但开箱即用,支持集合(List<T>

IReadOnlyList<T>

T[]

)和字典(目前

Dictionary<TKey,TValue>)、不同类型、二进制属性等。手册位于 图书馆存储库页面

© www.soinside.com 2019 - 2024. All rights reserved.