通过DataContract序列化为XML:自定义输出?

时间:2010-10-26 02:27:24

标签: c# serialization xml-serialization datacontractserializer

我有一个自定义Fraction类,我在整个项目中使用它。它很简单,它由一个构造函数组成,接受两个int并存储它们。我想使用DataContractSerializer来序列化我的项目中使用的对象,其中一些包括Fractions作为字段。理想情况下,我希望能够像这样序列化这样的对象:

<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文件设置为用户可编辑的(我将它们用作音乐游戏的输入,并且它们本质上充当记事本),并希望将符号保持为尽可能简化最终用户,因此他们不需要处理尽可能多的文本。

编辑:我还应该注意,我目前将我的Fraction类视为不可变(所有字段都是readonly),因此能够更改现有Fraction的状态不会有可能。但是,返回一个新的Fraction对象就可以了。

5 个答案:

答案 0 :(得分:6)

如果您添加一个表示Frac元素的属性并将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]);
        }
    }
}

答案 1 :(得分: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
        }
    }
}

答案 2 :(得分:3)

This MSDN article描述 IDataContractSurrogate 接口:

  

提供将一种类型替换为另一种类型所需的方法   DataContractSerializer在序列化,反序列化和   导出和导入XML模式文档。

虽然为时已晚,仍然可以帮助某人。实际上,允许为任何类更改XML。

答案 3 :(得分: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]);
    }
}

答案 4 :(得分:0)

您必须切换回XMLSerializer才能执行此操作。在能够自定义输出方面,DataContractSerializer的限制性更强。