【问题标题】:Serializing to XML via DataContract: custom output?通过 DataContract 序列化为 XML:自定义输出?
【发布时间】:2011-04-30 12:48:28
【问题描述】:

我有一个自定义的 Fraction 类,我在整个项目中都在使用它。这很简单,它由一个构造函数组成,接受两个整数并存储它们。我想使用 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 对象就可以了。

【问题讨论】:

  • 您介意解释一下为什么您更喜欢这种格式的输出吗?它可能会产生更中肯的答案,或者为您指明一个您没有想到的方向。
  • @shaunmartin 好点,重读我的问题我有点含糊。我稍后会编辑。

标签: c# serialization xml-serialization datacontractserializer


【解决方案1】:

如果您添加一个表示 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]);
        }
    }
}

【讨论】:

  • 不幸的是,Numerator 和 Denominator 是只读的,所以一旦创建了实例,我就无法分配给它们。
【解决方案2】:

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

【讨论】:

  • 像其他答案一样,如果不是因为 Numerator 和 Denominator 是只读的,这将是完美的。
  • 如果您不放弃只读字段,则可以将创建Fraction 对象实例的逻辑移至MyObject 级别
【解决方案3】:

This MSDN article 描述 IDataContractSurrogate 接口:

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

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

【讨论】:

    【解决方案4】:

    您可以使用 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]);
        }
    }
    

    【讨论】:

    • 这会很棒,但是我的 Fraction 类需要是不可变的,所以 Numerator 和 Denominator 只有 get acessors,而支持字段是只读的。
    • 我认为这个解决方案仍然适用于您 - 字段是不可变的,因为它们不公开任何公共 mutators 并且字段只写入一次 - 在反序列化期间。这不会满足您的需求吗?
    • 根据我的尝试,我认为情况并非如此(除非我做错了什么)。当我出于好奇尝试在 OnDeserialized 方法中设置分子和分母字段时,VS 对我大喊大叫,因为我试图在构造函数之外设置只读字段。
    • 可以理解。我提供的解决方案保证了不变性,但不是通过标记为readonly 的字段。相反,保证来自于没有公开的突变体被暴露的事实。这里的关键是,如果您使用带有私有 set 访问器而不是只读字段的自动属性,则此示例将适用于您。
    • 抱歉评论旧帖。但我相信这段代码会被序列化为 ...1/2.. 而不是 ...1 /2 ... 正如 OP 所期望的那样。
    【解决方案5】:

    您必须切换回 XMLSerializer 才能执行此操作。 DataContractSerializer 在能够自定义输出方面有一些限制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      相关资源
      最近更新 更多