5

Tôi có một lớp Phân số tùy chỉnh, mà tôi đang sử dụng trong toàn bộ dự án của mình. Nó đơn giản, nó bao gồm một hàm tạo duy nhất, chấp nhận hai int và lưu trữ chúng. Tôi muốn sử dụng DataContractSerializer để tuần tự hóa các đối tượng của tôi được sử dụng trong dự án của tôi, một số trong đó bao gồm Phân số dưới dạng các trường. Lý tưởng nhất, tôi muốn để có thể serialize đối tượng đó như thế này:Nối tiếp thành XML qua DataContract: đầu ra tùy chỉnh?

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

Trái ngược với điều này:

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

Có cách nào để làm điều này bằng DataContracts?

Tôi muốn làm điều này vì tôi định làm cho tệp XML có thể chỉnh sửa được (tôi đang sử dụng chúng làm đầu vào cho trò chơi âm nhạc và chúng hoạt động như notecharts), và muốn giữ ký hiệu càng ngắn càng tốt cho người dùng cuối, vì vậy họ sẽ không cần phải xử lý nhiều bức tường văn bản.

EDIT: Tôi cũng cần lưu ý rằng tôi hiện có lớp Phân số là không thay đổi (tất cả các trường là readonly), vì vậy có thể thay đổi trạng thái của Phân đoạn hiện tại. Tuy nhiên, việc trả về một đối tượng Fraction mới sẽ là OK.

+0

Bạn có thể giải thích * tại sao * bạn muốn đầu ra ở định dạng đó? Nó có thể tạo ra câu trả lời thích hợp hơn, hoặc chỉ cho bạn theo một hướng mà bạn đã không nghĩ đến. – shaunmartin

+0

@shaunmartin Điểm tốt, hãy đọc lại câu hỏi của tôi Tôi hơi mơ hồ. Tôi sẽ chỉnh sửa một chút. –

Trả lời

6

Nếu bạn thêm thuộc tính đại diện cho yếu tố frac và áp dụng các thuộc tính DataMember với nó chứ không phải là các thuộc tính khác, bạn sẽ có được những gì bạn muốn tôi tin rằng:

[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

Thật không may, Numerator và Mẫu số là chỉ đọc, vì vậy tôi không thể gán cho họ một khi dụ đã được tạo ra. –

0

Bạn sẽ phải chuyển về XMLSerializer để thực hiện điều đó. DataContractSerializer hơi hạn chế về mặt khả năng tùy chỉnh đầu ra.

5

DataContractSerializer sẽ sử dụng một tùy chỉnh IXmlSerializable nếu nó được cung cấp thay cho DataContractAttribute. Điều này sẽ cho phép bạn tùy chỉnh định dạng XML theo bất cứ cách nào bạn cần ... nhưng bạn sẽ phải tự tay mã hóa quá trình tuần tự hóa và deserialization cho lớp của bạn.

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

Giống như các câu trả lời khác, điều này sẽ là hoàn hảo nếu nó không phải là thực tế là Numerator và Mẫu số là chỉ đọc. –

+0

Đó là một cách dễ dàng cố định ... xem cập nhật của tôi –

+0

Nếu bạn không từ bỏ trên các trường chỉ đọc hơn bạn có thể di chuyển logic để tạo cá thể của đối tượng 'Phân số' thành cấp độ' MyObject' –

1

Bạn có thể làm điều này với DataContractSerializer, mặc dù theo cách khiến tôi cảm thấy khó chịu. Bạn có thể tận dụng lợi thế của thực tế là các thành viên dữ liệu có thể là biến riêng tư và sử dụng chuỗi riêng tư làm thành viên được tuần tự hóa của bạn. Bộ nối tiếp hợp đồng dữ liệu cũng sẽ thực hiện các phương thức tại các điểm nhất định trong quá trình được đánh dấu với các thuộc tính [On (De) Serializ (ed | ing)] - bên trong, bạn có thể kiểm soát cách các trường int được ánh xạ tới chuỗi, và ngược lại. Nhược điểm là bạn bị mất phép tự động serialization của DataContractSerializer trên lớp của bạn, và bây giờ có nhiều logic để duy trì.

Anyways, đây là những gì tôi sẽ làm:

[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

. tuyệt vời, nhưng lớp phân số của tôi cần phải không thay đổi, do đó, Numerator và mẫu số chỉ có được acessors, và các lĩnh vực ủng hộ là chỉ đọc. –

+1

Tôi nghĩ rằng giải pháp này sẽ vẫn hoạt động cho bạn - các trường là không thay đổi, vì chúng không phơi bày bất kỳ trình tắt công cộng nào và các trường chỉ được viết một lần - trong quá trình deserialization. Điều này sẽ không đáp ứng nhu cầu của bạn? – Ben

+0

Tôi không nghĩ rằng đó là trường hợp, từ những gì tôi đã cố gắng (trừ khi tôi đang làm một cái gì đó khủng khiếp sai). Khi tôi đã cố gắng thiết lập các tử số và mẫu số của tôi trong phương pháp OnDeserialized trước đó vì tò mò, VS hét lên với tôi vì cố gắng thiết lập các trường chỉ đọc bên ngoài của hàm tạo. –

3

This MSDN article mô tả IDataContractSurrogate Interface đó:

Cung cấp các phương pháp cần thiết để thay thế một loại cho người khác bởi DataContractSerializer trong serialization, deserialization, và xuất và nhập các tài liệu lược đồ XML.

Mặc dù quá trễ, vẫn có thể giúp ai đó.Trên thực tế, cho phép thay đổi XML cho bất kỳ lớp nào.

Các vấn đề liên quan