0

Lets say I have the following simple class:

[XmlRoot]
[XmlType("string")]
public partial class eString : IEnumerable<char>
{
    string AsString {get;set;}
    public IEnumerator<char> GetEnumerator()
    {
        return this.AsString.ToCharArray().ToList().GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.AsString.ToCharArray().GetEnumerator();
    }
    public void Add(char character)
    {
        this.AsString += character;
    }
}

Also a namedValue class:

[XmlRoot]
public partial class eNamedValue: KeyValuePair<eString,object>
{...}

And finally,

[XmlRoot]
public partial class eList<T>: List<eNamedValue>
{...}

Now, I serialize eList or any class that inherits from eList using the following XML serializer:

    public static XDocument Serialize<T>(this T obj) where T: new()
    {
        Type tt = obj.GetType();
        XmlSerializer xsSubmit = new XmlSerializer(tt);
        StringWriter sww = new StringWriter();
        XmlWriter writer = XmlWriter.Create(sww);
        xsSubmit.Serialize(writer, obj);
        return XDocument.Parse(sww.ToString());
    }

Serialization works - but my eString is being serialized as a character array, so instead of getting "string", I get individual characters values:

<eNamedList>
  <Key>99</Key>
  <Key>111</Key>
  <Key>100</Key>
  <Key>101</Key>...

Also, on Deserialization (SOLVED, see Update#1 below):

public static T Deserialize<T>(this XDocument xml) where T: new()
    {
        var serializer = new XmlSerializer(typeof(T));
        T result;

        using (TextReader reader = new StringReader(xml.ToString()))
        {
            result = (T)serializer.Deserialize(reader);
        }
        return result;
    }

I receive the following error:

System.Runtime.Serialization.SerializationException: Error in line 1 position 111. Expecting element 'eNamedList' from namespace 'http://schemas.datacontract.org/2004/07/Epic'.. Encountered 'Element' with name 'eNamedList', namespace ''.

So, my questions become:

  1. How do I control serialization of my eString, or any IEnumerable<char> and always serialize as a string?
  2. Why, when the element names match, do I get a failure on deserialization? (Am I just missing a namespace declaration?)

Thanks!


Update #1: So, I removed the IEnumerable<char> interface from my eString, and just left the IEnumerable<char>.GetEnumerator() method, which allows my string to be used AS an IEnumerable<char> while in a foreach loop, but serializes as a string. #WIN

Also, thanks to dbc, updated the original post with the XML Deserializer (rather than DataContract Serializer) and deserialization works.

turkinator
  • 905
  • 9
  • 25
  • 1
    Why are you using `XmlSerializer` to serialize but `DataContractSerializer` to deserialize? Is that intentional? – dbc Jul 21 '15 at 20:00
  • Thanks - You helped me notice I'd mixed up my DataContract serializer and XMLSerializer methods. Updated my post – turkinator Jul 21 '15 at 21:16

1 Answers1

3

To answer your questions:

  1. You probably shouldn't reinvent the wheel with custom string solutions. Regardless, if you want an (insanely) high-level of control over your (de-)serialization, you could implement IXmlSerializable and do the exact same thing yourself.

    [XmlRoot("string-wrapper")]
    public class CustomString : IXmlSerializable
    {
        public string Value { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null; // GetSchema should not be used.
        }
    
        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            bool isEmpty = reader.IsEmptyElement;
    
            reader.ReadStartElement();
            if (!isEmpty)
            {
                Value = reader.ReadString();
                reader.ReadEndElement();
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteString(Value);
        }
    }
    

Serialization of a CustomString now yields <string-wrapper>Testing</string-wrapper>. I'll post some test code at the end of this post.

  1. Your deserialization is likely broken because the XmlSerializer doesn't know that the IEnumerable you marked serializable should actually be treated like a string.

And now... Forget what I just told you. Unless you have very specific formatting requirements you should not implement your own version of a string. The built-in formatter knows a lot more formatting tricks (http://www.w3.org/TR/xmlschema-2/#built-in-datatypes), so you should probably let it do it's job.

Serializing classes is where the attributes come in, though I recommend you switch to the WFC data contracts, which may sound scary at first but actually provides a whole lot more for a lot less code. Again, I'm not sure what you're trying to accomplish, but trust me you don't want to get into the habit of hand writing XML.

If you're up for it you might like dynamic objects and the ExpandoObject (http://www.codeproject.com/Tips/227139/Converting-XML-to-an-dynamic-object-using-ExpandoO). These eliminate types all together and allow you to create dictionaries, arrays, named properties, whatever, all on the fly!

Finally, easy on the generics! Deserializing generic classes is not a trivial task. Besides, you probably don't need to. If you want your own collections, try the System.Collections.ObjectModel namespace. You don't have to worry about maintaining lists and implementing interfaces, just what you're actually storing:

class DictionaryOfStringsAndObjects : KeyedCollection<string, object {...}
class CollectionOfStrings : Collection<string> {...}

Also, try to avoid partial classes unless an ORM or a large legacy class forces it on you. You shouldn't actually use them unless you're made to.

All the best, and to a future devoid of XML!

public class CustomSerializer
{
    public static void Test()
    {
        var obj = new CustomString {Value = "Random string!"};
        var serializer = new CustomSerializer();
        var xml = serializer.Serialize(obj);
        Console.WriteLine(xml);

        var obj2 = serializer.Deserialize<CustomString>(xml);
    }

    public string Serialize(object obj)
    {
        var serializer = new XmlSerializer(obj.GetType());
        using (var io = new StringWriter())
        {
            serializer.Serialize(io, obj);
            return io.ToString();
        }
    }

    public T Deserialize<T>(string xml)
    {
        var serializer = new XmlSerializer(typeof (T));
        using (var io = new StringReader(xml))
        {
            return (T)serializer.Deserialize(io);
        }
    }
}
  • 1
    Did you test that implementation of `ReadXml()`? My understanding is that it needs to read the element end (and maybe check for the element being empty): see http://www.codeproject.com/Articles/43237/How-to-Implement-IXmlSerializable-Correctly and http://stackoverflow.com/questions/279534/proper-way-to-implement-ixmlserializable – dbc Jul 21 '15 at 20:02
  • 1
    I did, works fine; from what I can tell the [XmlType] is responsible for initiating and finalizing, and as this is a really simple class, well.. – diepvriezer Jul 21 '15 at 20:19
  • 1
    Also, OP, please note that `obj != obj2` whereas with strings this would be true. – diepvriezer Jul 21 '15 at 20:22
  • 1
    Your `ReadXml()` has a problem in that subsequent XML elements are not read properly. See https://dotnetfiddle.net/jBC09w for a demo. – dbc Jul 21 '15 at 20:41
  • You're absolutely right dbc, I've edited my post. And I messed up my comment scoring, so I can't upvote. New here. – diepvriezer Jul 21 '15 at 21:55
  • 1
    Turk, I'm curious, how many extension methods are we talking about haha? Also, you don't _have_ to use XML to store things, especially if you control the library and client apps. You should at least consider binary formatting (https://code.google.com/p/protobuf-net/) or JSON, the latter being devoid of user types making it much much easier to parse. – diepvriezer Jul 21 '15 at 22:04
  • @diepvriezer would you mind taking a look at a followup question? http://stackoverflow.com/questions/36315037/c-sharp-anonymous-generic-object-to-byte-without-binaryformatter-in-net-4 Thank you, either way. You've been very helpful. :-) – turkinator May 04 '16 at 05:26