33

一旦设置了初始值,我就有几个类是不可变的。Eric Lippert 将此称为一次写入不变性

在 C# 中实现一次写入不变性通常意味着通过构造函数设置初始值。这些值初始化只读字段。

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

有没有人对如何解决这个问题有建议?还有其他形式的不变性可以更好地与序列化配合使用吗?

编辑:正如@Todd 指出的那样,DataContractSerializer 不需要无参数构造函数。根据MSDN 上的 DataContractSerializer 文档,DataContractSerializer“不会调用目标对象的构造函数”。

4

5 回答 5

11

如果类是真正不可变的,只需使用标有属性的公共只读字段。

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

      public Immutable(string member)
      {
           Member = member;
      }
 }
于 2011-09-09T10:19:51.210 回答
11

假设这是您的“不可变”对象:

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

当你有一个不可变类型的属性时,不要序列化它,而是序列化一个 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; }
}

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

于 2009-08-18T14:53:03.597 回答
10

“Realio-trulio”不变性涉及构造函数。冰棒不变性是您可以做的,例如:

Person p = new Person();
p.Name = "Fred";
p.DateOfBirth = DateTime.Today;
p.Freeze(); // **now** immutable (edit attempts throw an exception)

(或与对象初始化器相同)

DataContractSerializer非常适合,只要您处理 on-serialized 回调来执行Freeze. XmlSerializer 不做序列化回调,所以更多的工作。

IXmlSerializable但是,如果您使用自定义序列化 ( ),则两者都适合。同样,自定义序列化对于realio -trulio 不可变性在很大程度上是可行的,但是很痛苦——这有点谎言,因为它是“创建一次,然后调用接口方法”——所以并不是真正不可变的。

要获得真正的不变性,请使用 DTO。

于 2009-08-18T14:53:18.883 回答
2

我建议看看这里的讨论:Why XML-Serializable class need a parameterless constructor

您应该考虑使用 DataContractSerializer。据我从文档中可以看出,这不需要无参数构造函数,并且可以序列化/反序列化私有成员。

于 2009-08-18T15:23:33.617 回答
1

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

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

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

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

于 2009-08-18T15:04:38.070 回答