views:

64

answers:

3

Here are my classes: http://pastebin.com/3dc5Vb1t

When I try to run

BookStore b = new BookStore();
b.LoadFromXML(Server.MapPath("list.xml"));
Label1.Text = b.ToString();

I get the following error:

You must implement a default accessor on System.Collections.Generic.LinkedList`1[[Book, App_Code.cxsacizw, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]] because it inherits from ICollection.

The error source is XmlSerializer s = new XmlSerializer(typeof(BookStore));

When I tried to look for a solution on google, I found it that LinkedList has some problems with serialization. How can I deal with it?

Thank you very much.

+4  A: 

It seems it's not possible.

Bug report here: linkedlist-t-can-not-be-serialized-using-the-xmlserializer. Where you can read the following:

Posted by Microsoft on 11/11/2004 at 19:35
We chose not to have a indexer method on LinkedList for performance reason. So LinkedList will not be XMLSeriliazable.

Nifle
If so, what can I do? I don't want to use an array, as I'm not sure how many items will the array include.
iTayb
You use a List http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx
kervin
+3  A: 

See http://stackoverflow.com/questions/169973/when-should-i-use-a-list-vs-a-linkedlist

If you're using a linked list, it probably assumed you're doing so for performance reasons. Otherwise a List<T> may be more appropriate.

kervin
+1  A: 

In the abstract:

Some .Net types like linked lists, hash-tables (dictionaries), etc have some issues when you attempt to serialize them. This seems to be mostly by design: there are other, simpler, types that can represent the same range of values (for example, a normal list instead of a linked-list, or a list of pairs instead of a dictionary), so the .Net assumes that if you are using the more specific type you want its specific features. When such features can't be serialized (for example, a hash-table can't be described as such in XML), problems begin.

The key point is: do you really need the specific features of these types on their serialized form? For example, if you to serialize a linked list so the serialized version includes the links between the elements, then you are going to have serious headaches. Fortunately, on most cases, you will only need the special features when you are actually working with the object, so you can serialize a simplified (but complete enough) version of it and reconstruct the advanced object upon deserialization.

To make the above possible, .Net includes some useful tools to meddle with the de/serialization process. First, you should always mark your serializable objects as such with the System.SerializableAttribute (http://msdn.microsoft.com/en-us/library/system.serializableattribute.aspx). Next, you can implement System.Runtime.Serialization.ISerializable (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx) to have full control on the serialization process. On the simplest case, all you need to do is to convert your linked list to a normal one and add it to the SerializationInfo argument you get within GetObjectData(...) as a single value (I'll assume you tag it as "value") for serialization. Then, to enable deserialization, add a constructor like the example below.

However, that only covers the shared serialization infrastructure. To get full control with XML serialization, you need to implement System.Xml.Serialization.IXmlSerializable. When doing so, keep in mind that the writer will implicitly wrap your output inside an element that denotes the type of object being serialized; and the reader needs to explicitly dig inside that element (on some cases, this asymetry can be needed). It may be tricky to implement this interface if you aren't used to .Net's XML streams; but lucky for you I had to do something similar with dictionaries a while ago and I could recycle most of the code ;).

To the specific: This example provides the essentials for a LinkedList that is serialized as a "normal" list, and deserializes back to a linked list. The serialized form does not contain the inter-element links; but those links are reliably remade upon deserialization.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Xml;
using System.IO;

namespace WFTest {
    [Serializable]
    class SerializableLinkedList<T>: LinkedList<T>, ISerializable, IXmlSerializable {
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("value", this.ToList());
        }
        // Implied by ISerializable, but interfaces can't actually define constructors:
        SerializableLinkedList(SerializationInfo info, StreamingContext context)
            : base((IEnumerable<T>)info.GetValue("value", typeof(List<T>))) { }

        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { return null; }

        void IXmlSerializable.ReadXml(XmlReader reader) {
            this.Clear(); // Start with an empty list
            reader.ReadStartElement(); // Skips the opening tag
            while (reader.LocalName=="item") { // Retrieve all elements:
                T value;
                if(reader.IsEmptyElement) { // If element is empty...
                    value=default(T); // the item's value falls back to default(T)
                    reader.ReadStartElement(); // and consume the (empty) element
                } else {
                    // IIRC, ReadInnerXml() consumes the outer tag, despite not returning them.
                    value=(T)((new XmlSerializer(typeof(T))).Deserialize(new StringReader(reader.ReadInnerXml())));
                }
                this.AddLast(value);
            }
            reader.ReadEndElement(); // Consumes the remaining closing tag from the reader
        }

        void IXmlSerializable.WriteXml(XmlWriter writer) {
            foreach(T item in this) {
                // Format the item itself:
                StringBuilder sb=new StringBuilder();
                (new XmlSerializer(typeof(T))).Serialize(XmlWriter.Create(sb), item);
                XmlDocument doc=new XmlDocument();
                doc.LoadXml(sb.ToString());
                // and now write it to the stream within <item>...</item> tags
                writer.WriteStartElement("item");
                writer.WriteRaw(doc.DocumentElement.OuterXml);
                writer.WriteEndElement(); // </item>
            }
        }
    }
}

Use this class instead of the "raw" LinkedList class for your objects (or as a base class if you need to derive from LinkedList), and serialization shouldn't trigger any more issues with the lists. Note, however, that whatever you use as the "T" parameter for this list must be itself serializable, but there is no way to enforce such requirement in code.

As an aside, let me deal with some legal stuff: as the author of the code snippet above, I grant a non-revocable, non-exclusive, worldwide permission to anyone to use it for any purposes (including, but not limited to, creating derivative works of any kind, and distributing them on any form). Attribution is not required, but it's always welcome.

Oh, and after a look at your code, I'd highly encourage you to use StringBuilder for your ToString() method's implementation: each time your code calls += on a String, a new string object is created (taking time and memory). While it's unlikely that you'll run out of memory with that, a significantly long list may easily trigger a performance impact on your application.

Hope this helps

herenvardo