5

У меня есть собственный класс фракций, который я использую на протяжении всего моего проекта. Это просто, он состоит из одного конструктора, принимает два ints и сохраняет их. Я хотел бы использовать DataContractSerializer для сериализации моих объектов, используемых в моем проекте, некоторые из которых включают в себя поля Fractions. В идеале я хотел бы иметь возможность сериализовать такие объекты, как это:Сериализация в XML через DataContract: пользовательский вывод?

<Object> 
    ... 
    <Frac>1/2</Frac> // "1/2" would get converted back into a Fraction on deserialization. 
    ... 
</Object> 

В противоположность этому:

<Object> 
    ... 
    <Frac> 
     <Numerator>1</Numerator> 
     <Denominator>2</Denominator> 
    </Frac> 
    ... 
</Object> 

Есть ли способ сделать это с помощью DataContracts?

Я хотел бы сделать это, потому что планирую сделать редактируемые XML-файлы (я использую их в качестве входных данных для музыкальной игры, и они действуют как записные заметки, по существу), и вы хотите сохранить как можно более короткое обозначение для конечного пользователя, поэтому им не нужно иметь дело со столькими стенами текста.

EDIT: Я хотел бы также отметить, что я в настоящее время у меня есть класс Fraction как неизменная (все поля являются readonly), поэтому возможность изменить состояние существующей фракции не было бы возможным. Однако возвращение нового объекта Fraction будет в порядке.

+0

Не могли бы вы объяснить, почему * вы предпочтете вывод в этом формате? Это может привести к более уместным ответам или указать вам направление, о котором вы не думали. – shaunmartin

+0

@shaunmartin Хороший вопрос, перечитывая мой вопрос, я был немного расплывчатым. Я немного отредактирую. –

ответ

6

Если Вы добавляете свойство, представляющий элемент гидроразрыва и применить атрибут DataMember к нему, а не другим свойствам, которые вы получите то, что вы хотите, я верю:

[DataContract] 
public class MyObject { 
    Int32 _Numerator; 
    Int32 _Denominator; 
    public MyObject(Int32 numerator, Int32 denominator) { 
     _Numerator = numerator; 
     _Denominator = denominator; 
    } 
    public Int32 Numerator { 
     get { return _Numerator; } 
     set { _Numerator = value; } 
    } 
    public Int32 Denominator { 
     get { return _Denominator; } 
     set { _Denominator = value; } 
    } 
    [DataMember(Name="Frac")] 
    public String Fraction { 
     get { return _Numerator + "/" + _Denominator; } 
     set { 
      String[] parts = value.Split(new char[] { '/' }); 
      _Numerator = Int32.Parse(parts[0]); 
      _Denominator = Int32.Parse(parts[1]); 
     } 
    } 
} 
+0

К сожалению, числитель и знаменатель являются readonly, поэтому я не могу назначить их после создания экземпляра. –

0

Для этого вам нужно будет вернуться к XMLSerializer. DataContractSerializer является немного более ограничительным с точки зрения возможности настройки вывода.

5

DataContractSerializer будет использовать пользовательский IXmlSerializable если он вместо DataContractAttribute. Это позволит вам настроить форматирование XML в любом случае вам нужно ... но вам нужно будет обработать процесс сериализации и десериализации для вашего класса.

public class Fraction: IXmlSerializable 
{ 
    private Fraction() 
    { 
    } 
    public Fraction(int numerator, int denominator) 
    { 
     this.Numerator = numerator; 
     this.Denominator = denominator; 
    } 
    public int Numerator { get; private set; } 
    public int Denominator { get; private set; } 

    public XmlSchema GetSchema() 
    { 
     throw new NotImplementedException(); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     var content = reader.ReadInnerXml(); 
     var parts = content.Split('/'); 
     Numerator = int.Parse(parts[0]); 
     Denominator = int.Parse(parts[1]); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteRaw(this.ToString()); 
    } 

    public override string ToString() 
    { 
     return string.Format("{0}/{1}", Numerator, Denominator); 
    } 
} 
[DataContract(Name = "Object", Namespace="")] 
public class MyObject 
{ 
    [DataMember] 
    public Fraction Frac { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var myobject = new MyObject 
     { 
      Frac = new Fraction(1, 2) 
     }; 

     var dcs = new DataContractSerializer(typeof(MyObject)); 

     string xml = null; 
     using (var ms = new MemoryStream()) 
     { 
      dcs.WriteObject(ms, myobject); 
      xml = Encoding.UTF8.GetString(ms.ToArray()); 
      Console.WriteLine(xml); 
      // <Object><Frac>1/2</Frac></Object> 
     } 

     using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml))) 
     { 
      ms.Position = 0; 
      var obj = dcs.ReadObject(ms) as MyObject; 

      Console.WriteLine(obj.Frac); 
      // 1/2 
     } 
    } 
} 
+0

Как и другие ответы, это было бы прекрасно, если бы не тот факт, что числитель и знаменатель - только для чтения. –

+0

Это легко исправлено ... см. Мое обновление –

+0

Если вы не откажетесь от полей readonly, вы можете переместить логику, чтобы создать экземпляр объекта 'Fraction' до уровня' MyObject' –

1

Вы можете сделать это с DataContractSerializer, хотя таким образом, что чувствует Hacky ко мне. Вы можете воспользоваться тем фактом, что члены данных могут быть частными переменными и использовать приватную строку в качестве вашего сериализованного элемента. Сериализатор данных также будет выполнять методы в определенных точках процесса, которые помечены атрибутами [On (De) Serializ (ed | ing)] - внутри них вы можете контролировать, как поля int сопоставляются с строкой, и наоборот. Недостатком является то, что вы теряете автоматическую мануальную сериализацию DataContractSerializer в своем классе и теперь имеете больше логики для поддержки.

В любом случае, вот что я хотел бы сделать:

[DataContract] 
public class Fraction 
{ 
    [DataMember(Name = "Frac")] 
    private string serialized; 

    public int Numerator { get; private set; } 
    public int Denominator { get; private set; } 

    [OnSerializing] 
    public void OnSerializing(StreamingContext context) 
    { 
     // This gets called just before the DataContractSerializer begins. 
     serialized = Numerator.ToString() + "/" + Denominator.ToString(); 
    } 

    [OnDeserialized] 
    public void OnDeserialized(StreamingContext context) 
    { 
     // This gets called after the DataContractSerializer finishes its work 
     var nums = serialized.Split("/"); 
     Numerator = int.Parse(nums[0]); 
     Denominator = int.Parse(nums[1]); 
    } 
} 
+0

Это было бы удивительный, но мой класс фракций должен быть неизменным, поэтому Numerator и Denominator получают только те, у кого есть поля, а поля поддержки - только для чтения. –

+1

Я бы подумал, что это решение по-прежнему будет работать для вас - поля неизменны, поскольку они не раскрывают никаких публичных мутаторов, а поля записываются только один раз - во время десериализации. Это не удовлетворит ваши потребности? – Ben

+0

Я не думаю, что это так, от того, что я пробовал (если я не делаю что-то ужасно неправильно). Когда я попытался установить поля числителя и знаменателя в методе OnDeserialized ранее из любопытства, VS кричал на меня, пытаясь установить поля только для чтения вне конструктора. –

3

This MSDN article описывает IDataContractSurrogate интерфейса, который:

предоставляет методы, необходимых для замены одного типа к другому по DataContractSerializer во время сериализации, десериализация и экспорт и импорт документов схемы XML.

Хотя слишком поздно, все равно может помочь кому-то.На самом деле, позволяет изменять XML для ЛЮБОГО класса.

Смежные вопросы