5

我有一個自定義的Fraction類,我在整個項目中都使用它。很簡單,它由一個構造函數組成,接受兩個int並存儲它們。我想使用DataContractSerializer來序列化我的項目中使用的對象,其中一些包括分區作爲字段。理想情況下,我想能夠序列化這樣的對象是這樣的:通過DataContract序列化爲XML:自定義輸出?

<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文件用戶可編輯(我將它們用作音樂遊戲的輸入,它們本質上充當了notecharts),並且希望保留對最終用戶來說盡可能簡潔,所以他們不需要處理儘可能多的文字牆。

編輯:我也應該注意到,目前,我有我的Fraction類爲不可變的(所有字段爲readonly),因此能夠改變現有分數的狀態是不可能的。不過,返回一個新的Fraction對象是可以的。

+0

你介意解釋一下爲什麼你更喜歡那種格式的輸出嗎?它可能會產生更多相關的答案,或者指向一個你沒有想到的方向。 – shaunmartin 2010-10-26 03:11:34

+0

@shaunmartin好點,重讀我的問題我有點含糊。我會稍微編輯一下。 – 2010-10-26 05:21:47

回答

6

如果添加了代表壓裂元素的屬性和數據成員屬性應用到它,而不是其他的屬性,你會得到你想要什麼,我相信:

[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

不幸的是,分子和分母是隻讀的,所以一旦創建了實例,我就不能分配給它們。 – 2010-10-28 02:18:07

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

像其他答案一樣,如果不是因爲分子和分母是隻讀的這一事實,這將是完美的。 – 2010-10-28 02:16:45

+0

這很容易修復...看我的更新 – 2010-10-28 04:14:53

+0

如果你不會放棄在只讀字段比你可以移動邏輯創建'Fraction'對象的實例到'MyObject'的水平 – 2010-10-28 04:18:19

1

你可以用DataContractSerializer來做到這一點,儘管這樣做對我來說很難受。您可以利用數據成員可以是私有變量的事實,並使用私有字符串作爲序列化成員。數據協定序列化程序還將在過程中的某些點上執行標有[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

這應該是真棒,但我的分數類需要是不可變的,所以分子和分母只能獲得分配器,並且後備字段是隻讀的。 – 2010-10-28 02:15:11

+1

我認爲這個解決方案仍然適用於你 - 在反序列化期間,這些字段是不可變的,因爲它們不公開任何公開的mutators,並且這些字段只寫入一次。這會不會滿足您的需求? – Ben 2010-10-28 02:28:36

+0

我不認爲是這種情況,從我嘗試過的(除非我正在做一些可怕的錯誤)。當我試圖在OnDeserialized方法中設置我的分子和分母字段時,出於好奇,VS向我大吼,試圖在構造函數外設置只讀字段。 – 2010-10-28 04:56:50

3

This MSDN article介紹IDataContractSurrogate接口,其中:

提供了由 DataContractSerializer的序列化過程中替換一個類型爲另一個所需的方法,反序列化,以及導出和導入XML模式文檔。

雖然方式太晚,還是可以幫助別人。實際上,允許更改任何類的XML。