views:

33

answers:

1

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 able 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.

A: 

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.

VinayC
Yeah, the simple way won't work in my case, sadly. I'll try taking a stab at the Data Contract Surrogates, but it looks like it's quite a dive; so far, I'm not understanding any of it.
Mark LeMoine
@Mark, for DataContract Surrogates, see example from MSDN:http://msdn.microsoft.com/en-us/library/ms751540.aspx and also, this article:http://nathannorthcutt.com/?p=88. However, I don't think that your problem will be solved by that also. Data Contract Serialization does not allows complete schema control - its main aim is to have simple serialization format that can easily adopted (and deserialized). For full schema control, XMLSerializer is the way to go. For setting read-only fields, you can use reflection.
VinayC