Подтвердить что ты не робот

Неизменность и XML-сериализация

У меня есть несколько классов, которые неизменяемы после установки их начальных значений. Эрик Липперт называет эту постоянную однократную запись.

Реализация однократной неизменяемости в С# обычно означает установку начальных значений через конструктор. Эти значения инициализируют поля только для чтения.

Но если вам нужно сериализовать такой класс в XML, используя либо XmlSerializer, либо DataContractSerializer, вы должны иметь конструктор без параметров.

Есть ли у кого-нибудь предложения по решению этой проблемы? Существуют ли другие формы неизменяемости, которые лучше работают с сериализацией?

EDIT: Как отметил @Todd, DataContractSerializer не требует конструктора без параметров. Согласно документации DataContractSerializer на MSDN, DataContractSerializer "не вызывает конструктор целевого объекта."

4b9b3361

Ответ 1

Предполагая, что это ваш "неизменный" объект:

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

Хорошо, это немного длиннее для того, что выглядит так просто... но оно должно работать;)

Ответ 2

"Реалио-trulio" неизменность включает конструктор. Непостоянство Popsicle - это то, где вы можете сделать, например:

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.

Ответ 3

Если класс действительно неизменен, просто используйте общедоступные поля readonly, помеченные атрибутами.

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

      public Immutable(string member)
      {
           Member = member;
      }
 }

Ответ 4

Я рекомендую взглянуть на обсуждение здесь: Зачем классу XML-Serializable нужен конструктор без параметров.

Вам следует рассмотреть возможность использования DataContractSerializer. Насколько я могу судить по документам, это не требует конструктора без параметров и может сериализовать/десериализовать частные члены.

Ответ 5

Я только что посмотрел статью, с которой вы связались. В его терминологии объекты, использующие поля readonly, инициализированные в конструкторе, называются "неизменяемой только для записи".

"Неповторимость Popsicle" немного отличается. Lippert дает два примера того, где это было бы полезно: десериализация (проблема, которую вы пытаетесь решить) и круговые ссылки, где A и B необходимо создавать независимо, но потребности имеют ссылку на B, а B - ссылку на A.

Два других очевидных способа достижения этого результата были упомянуты здесь (как я писал это, по сути). Образец, который Томас Левесек упоминает, в основном представляет собой образец "Строителя". Но в этом случае это довольно громоздко, потому что вам нужно не только перейти от Builder к неизменяемому, но и от неизменяемого к строителю, чтобы сериализация/десериализация была симметричной.

Таким образом, шаблон "блокировки", как упоминал Марк Гравелл, кажется более полезным. Я не знаком с С#, хотя, поэтому я не уверен, как лучше всего реализовать его. Я думаю, вероятно, частная собственность, такая как заблокированная. Затем все методы getter должны явно проверять, заблокирован ли объект (он же "заморожен" ). Если это так, они должны выдать исключение (это ошибка времени выполнения, вы не можете обеспечить неизменность popstick во время компиляции).