有没有办法覆盖我的自定义类型通过DataContract序列化时转换为什么字符串值?

时间:2010-10-28 04:47:05

标签: c# serialization xml-serialization datacontract

在我的音乐/节奏游戏中,我使用序列化来保存用户创建的simfiles(想想音乐标签或记事本)。那里没什么太惊天动地的。但是,我正在使用DataContract来执行序列化,因为:

1)我还需要序列化私有和受保护的字段。我不在乎它们是否可见,主要是因为...
2)我希望用户能够在他/她喜欢的文本编辑器中编辑序列化文件,并能够将这些文件加载​​到游戏中(这些文件将用于表示游戏中的音符,想想{{ 3}})。

我想要序列化的一个自定义数据类型是我创建的Fraction类:

using System.Runtime.Serialization;

namespace Fractions {
    [DataContract(Namespace="")] // Don't need the namespaces.
    public sealed class Fraction {
        // NOTE THAT THESE ARE "READONLY"!
        [DataMember(Name="Num", Order=1)] private readonly long numer;
        [DataMember(Name="Den", Order=2)] private readonly long denom;

        // ...LOTS OF STUFF...

        public static Fraction FromString(string str) {
            // Try and parse string and create a Fraction from it, and return it.
            // This is static because I'm returning a new created Fraction.
        }

        public override ToString() {
            return numer.ToString() + "/" + denom.ToString();
        }
    }
}

测试它,它可以正常工作,序列化为以下形式的XML片段:

<Fraction>
   <Num>(INT64 VALUE AS STRING)</Num>
   <Den>(INT64 VALUE AS STRING)</Den>
</Fraction>

现在,我可以对此感到高兴并继续我的快乐编码方式。 但我很挑剔。

我的最终用户可能不会非常熟悉XML,并且我的游戏中有很多更复杂的数据类型,其中包含很多分数,所以我更愿意能够代表一个分数。这样的XML simfile(更简洁):

<Fraction>(NUMERATOR)/(DENOMINATOR)</Fraction>

但是,我不知道如何在不破坏自动(反)序列化的情况下执行此操作。我查看了IXmlSerializable接口,但由于我的数据类型需要是可变的才能使它工作(ReadXml()不会返回一个新的Fraction对象,而是关闭似乎闪存 - 实例化一个,您必须手动填写值,由于readonly而无效。由于相同的原因,使用OnSerializing和OnDeserialized属性不起作用。我真的更喜欢保持我的Fraction类不可变。

我猜测有一个标准的过程,在序列化为XML时,基本原理会转换为字符串。例如,任何数字基元都必须在序列化/反序列化时转换为字符串/从字符串转换。有没有办法让我能够从转换到我的Fraction类型添加这种自动字符串?如果有可能的话,我认为序列化过程看起来像这样:

1)序列化包含分数字段的复杂数据类型 2)开始序列化[DataMember]字段 3)嘿,这个字段是一个Fraction对象。分数能够完全表示为字符串。将该字符串直接写入XML,而不是潜入Fraction对象并写出其所有字段 ...

反序列化将采用相反的方式:

1)反序列化这些数据,我们期待某某数据类型 2)开始反序列化字段 3)哦看,一个Fraction对象,我可以继续读取内容字符串,将该字符串转换为新的Fraction对象,并返回新创建的分数。
...

我有什么办法可以做到这一点吗?谢谢!

编辑:数据合同代理似乎是要走的路,但对于我的生活,我似乎无法理解他们或让他们在我的游戏中工作。或者更确切地说,他们为我的序列化元素添加了一些讨厌的自动命名空间和ID字段。

2 个答案:

答案 0 :(得分:0)

我猜你可以使用Data Contract Surrogates

但更简单的方法是在你的类型中有一个私有字符串成员fractionString,它代表你的Type的字符串表示。您必须仅在对象构造期间初始化它(因为您的类型是不可变的)。现在,您可以从序列化中跳过num和den,并将fractionString字段标记为DataMember。该方法的缺点是额外的空间消耗。

public sealed class Fraction {

    private readonly long numer;
    private readonly long denom;

    [DataMember(Name="Fraction")
    private string fractionString;

编辑:没关系,只需重新阅读您想要的内容并实现上述功能即可。

答案 1 :(得分:0)

我有类似的问题,但在我的情况下,我使用的是Type-Safe-Enumeration模式。在每种情况下,当您编写DataContract时,您在包含非简单数据类型的元素中指定C#决定将该类放入元素中,然后查找该数据合同的类。

不幸的是,这不是我们任何一个人想要的。

我的解决方案分为两部分:
1)在我们想要包含的复杂类中(在您的情况下为Fraction)提供了将对象序列化和反序列化为字符串的方法。 (我使用了强制转换操作符,因为它对我来说意义最大):

class Complex
{
    ...
    // NOTE: that this can be implicit since every Complex generates a valid string        
    public static implicit operator string(Complex value)
    {
        return <... code to generate a string from the Complex Type...>;
    } 
    // NOTE: this must be explicit since it can throw an exception because not all
    // strings are valid Complex types
    public static explicit operator Complex(string value)
    {
        return <... code to validate and create a Complex object from a string ...>;
    }
    ...
 }

2)现在,当您在另一个类中使用复杂类型对象时,可以使用字符串属性定义DataContract并使用实际复杂类型的支持值:

[DataContract]
class User
{
    ...
    [DataMember]
    public string MyComplex
    {
         get { return m_MyComplex; }
         set { m_myComplex = (Complex)value; }
    }
    // NOTE that this member is _not_ part of the DataContract
    Complex m_myComplex;
    ...
}

虽然我承认它不是一个理想的解决方案,需要在using类中存在额外的代码,但它允许在DataContract格式中对复杂类进行任意字符串表示,而不需要额外的层。