开发者

Is there any way to override what string value my custom type gets converted to when serializing via DataContract?

In my music/rhythm game, I using serialization in order to save user-created simfiles (think music tabs or notecharts). Nothing too earth-shattering there. But, I'm using a DataContract in order to perform the serialization, because:

1) I need private and protected fields serialized as well. I don't care if they're visible, mainly due to...

2) I'd like for the user to be able to edit the serialized file in his/her favorite text editor and be ab开发者_如何学JAVAle to load these into the game (these files will be used to represent musical notes in the game, think StepMania simfiles).

One of my custom datatypes I'd like to serialize is a Fraction class I've created:

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();
        }
    }
}

Testing this, it works decently, serializing into an XML fragment of the form:

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

Now, I could just be happy with this and go on my merry coding way. But I'm picky.

My end users will probably not be super familiar with XML, and there's a LOT of more complex datatypes in my game that will include a lot of Fractions, so I'd much rather prefer to be able to represent a Fraction in the XML simfile as such (much more concisely):

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

However, I'm at a loss as to how to do this without breaking automatic (de)serialization. I looked into the IXmlSerializable interface, but I was shut down by the fact that my datatype needed to be mutable in order for it to work (ReadXml() doesn't return a new Fraction object, but instead seems to flash-instantiate one, and you have to fill in the values manually, which doesn't work due to the readonly). Using the OnSerializing and OnDeserialized attributes didn't work either for the same reason. I'd REALLY prefer to keep my Fraction class immutable.

I'm guessing there's a standard procedure by which primitives are converted to/from strings when serializing to XML. Any numeric primitives, for instance, would have to be converted to/from strings upon serializing/deserializing. Is there any way for me to be able to add this sort of automatic string from/to conversion to my Fraction type? If it were possible, I'd imagine the serializing procedure would look something like this:

1) Serialize this complex datatype which contains Fraction fields.

2) Start serializing the [DataMember] fields.

3) Hey, this field is a Fraction object. Fractions are able to represent themselves fully as a string. Write that string out to the XML directly, instead of diving into the Fraction object and writing out all its fields.

...

Deserialization would work the opposite way:

1) Deserialize this data, we're expecting so-and-so data type.

2) Start deserializing fields.

3) Oh look, a Fraction object, I can just go ahead and read the content string, convert that string into a new Fraction object, and return the newly-created Fraction.

...

Is there anything I can do to accomplish this? Thanks!

EDIT: Data Contract Surrogates seem like the way to go, but for the life of me I can't seem to understand them or have them work in my game. Or rather, they add some nasty automatic namespace and ID fields to my serialized elements.


I guess that you can probably use Data Contract Surrogates.

But even simpler way would be to have a private string member fractionString within your type that will represent the string representation of your Type. You have to initialize it only during object construction (as your type is immutable). Now you can skip num and den from serialization and mark your fractionString field as DataMember. Downside to the approach is additional space consumption.

public sealed class Fraction {

    private readonly long numer;
    private readonly long denom;

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

EDIT: Never mind, just re-read what you want and realized above won't work.


I had a similar problem, though in my case I was using the Type-Safe-Enumeration pattern. In each case, when you write your DataContract, you are specifying in the elements containing the non-simple data type C# decides to make the class into an element and then look to the class for that data contract.

Unfortunately, that is not what either of us wants.

My solution was two part:
1) In the complex class we want to include (Fraction in your case) provide methods to serialize and deserialize the object into a string. (I used the cast operators because that carries the meaning best to me):

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) Now, when you use a Complex type object in another class, you define the DataContract with a string property and use a backing value of the actual Complex type:

[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;
    ...
}

While I admit it is not an ideal solution, requiring the presence of additional code in the using class, it does allow arbitrary string representation of a complex class within the DataContract format without additional layers that would otherwise be necessary.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜