XML Generische Liste serialisierbarer Objekte serialisieren

72

Kann ich eine generische Liste serialisierbarer Objekte serialisieren, ohne deren Typ angeben zu müssen?

So etwas wie die Absicht hinter dem kaputten Code unten:

List<ISerializable> serializableList = new List<ISerializable>();

XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());

serializableList.Add((ISerializable)PersonList);

using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xmlSerializer.Serialize(streamWriter, serializableList);
}

Bearbeiten:

Für diejenigen, die Details wissen wollten: Wenn ich versuche, diesen Code auszuführen, tritt in der XMLSerializer-Zeile [...] ein Fehler auf mit:

Schnittstelle System.Runtime.Serialization.ISerializable kann nicht serialisiert werden.

Wenn ich zu ändere, List<object>bekomme ich "There was an error generating the XML document.". Das InnerException-Detail ist"{"The type System.Collections.Generic.List1[[Project1.Person, ConsoleFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] may not be used in this context."}"

Das Personenobjekt ist wie folgt definiert:

[XmlRoot("Person")]
public class Person
{
    string _firstName = String.Empty;
    string _lastName = String.Empty;

    private Person()
    {
    }

    public Person(string lastName, string firstName)
    {
        _lastName = lastName;
        _firstName = firstName;
    }

    [XmlAttribute(DataType = "string", AttributeName = "LastName")]
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    [XmlAttribute(DataType = "string", AttributeName = "FirstName")]
    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }
}

Die PersonList ist nur eine List<Person>.

Dies ist jedoch nur zum Testen gedacht, daher waren die Details nicht zu wichtig. Der Schlüssel ist, dass ich ein oder mehrere verschiedene Objekte habe, die alle serialisierbar sind. Ich möchte sie alle in eine Datei serialisieren. Ich dachte, der einfachste Weg, dies zu tun, wäre, sie in eine generische Liste aufzunehmen und die Liste auf einmal zu serialisieren. Das funktioniert aber nicht.

Ich habe es auch mit versucht List<IXmlSerializable>, aber das scheitert mit

System.Xml.Serialization.IXmlSerializable cannot be serialized because it does not have a parameterless constructor.

Entschuldigen Sie den Mangel an Details, aber ich bin ein Anfänger und weiß nicht, welche Details erforderlich sind. Es wäre hilfreich, wenn Leute, die nach mehr Details fragen, versuchen würden, so zu antworten, dass ich verstehe, welche Details erforderlich sind, oder eine grundlegende Antwort mit möglichen Anweisungen.

Auch dank der beiden Antworten, die ich bisher erhalten habe, hätte ich viel mehr Zeit mit Lesen verbringen können, ohne diese Ideen zu bekommen. Es ist erstaunlich, wie hilfreich die Leute auf dieser Seite sind.

Simon D.
quelle
1
Ich habe es versucht, es hat nicht funktioniert, ich kann nicht herausfinden, wie es funktioniert. Ich habe versucht, es in List <Objekt> zu ändern, das hat auch nicht funktioniert.
Simon D
Inwiefern hat es "nicht funktioniert"? Geben Sie Details an, wenn Sie Antworten wünschen.
John Saunders
Was war der Fehler, den Sie hatten? Implementieren alle Objekte ISerializableoder haben sie nur das [Serializable]Attribut?
Erich Mirabal
John: Das obige Beispiel (wenn es für die Arbeit mit List <Object> angepasst wurde) würde eine Ausnahme ergeben, die erklärt, dass die spezifischen Typen nicht erwartet wurden.
Robert Venables
Der parameterlose Konstruktor weist Fehler auf, da die Klasse, die Sie serialisieren möchten, kein Konstrukt enthält, das keine Argumente akzeptiert. Definieren Sie einen privaten <ClassName> () {} für jede der Klassen, und das wird verschwinden.
Ian

Antworten:

79

Ich habe eine Lösung für eine generische Liste <> mit dynamisch gebundenen Elementen.

Klasse PersonalList ist das Stammelement

[XmlRoot("PersonenListe")]
[XmlInclude(typeof(Person))] // include type class Person
public class PersonalList
{
    [XmlArray("PersonenArray")]
    [XmlArrayItem("PersonObjekt")]
    public List<Person> Persons = new List<Person>();

    [XmlElement("Listname")]
    public string Listname { get; set; }

    // Konstruktoren 
    public PersonalList() { }

    public PersonalList(string name)
    {
        this.Listname = name;
    }

    public void AddPerson(Person person)
    {
        Persons.Add(person);
    }
}

Klasse Person Es ist ein einzelnes Listenelement

[XmlType("Person")] // define Type
[XmlInclude(typeof(SpecialPerson)), XmlInclude(typeof(SuperPerson))]  
        // include type class SpecialPerson and class SuperPerson
public class Person
{
    [XmlAttribute("PersID", DataType = "string")]
    public string ID { get; set; }

    [XmlElement("Name")]
    public string Name { get; set; }

    [XmlElement("City")]
    public string City { get; set; }

    [XmlElement("Age")]
    public int Age { get; set; }

    // Konstruktoren 
    public Person() { }

    public Person(string name, string city, int age, string id)
    {
        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
    }
}

Klasse SpecialPerson erbt Person

[XmlType("SpecialPerson")] // define Type
public class SpecialPerson : Person
{
    [XmlElement("SpecialInterests")]
    public string Interests { get; set; }

    public SpecialPerson() { }

    public SpecialPerson(string name, string city, int age, string id, string interests)
    {
        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
        this.Interests = interests;
    }
}

Klasse SuperPerson erbt Person

[XmlType("SuperPerson")] // define Type
public class SuperPerson : Person
{
    [XmlArray("Skills")]
    [XmlArrayItem("Skill")]
    public List<String> Skills { get; set; }

    [XmlElement("Alias")]
    public string Alias { get; set; }

    public SuperPerson() 
    {
        Skills = new List<String>();
    }

    public SuperPerson(string name, string city, int age, string id, string[] skills, string alias)
    {
        Skills = new List<String>();

        this.Name = name;
        this.City = city;
        this.Age = age;
        this.ID = id;
        foreach (string item in skills)
        {
            this.Skills.Add(item);   
        }
        this.Alias = alias;
    }
}

und die Haupttestquelle

static void Main(string[] args)
{
    PersonalList personen = new PersonalList(); 
    personen.Listname = "Friends";

    // normal person
    Person normPerson = new Person();
    normPerson.ID = "0";
    normPerson.Name = "Max Man";
    normPerson.City = "Capitol City";
    normPerson.Age = 33;

    // special person
    SpecialPerson specPerson = new SpecialPerson();
    specPerson.ID = "1";
    specPerson.Name = "Albert Einstein";
    specPerson.City = "Ulm";
    specPerson.Age = 36;
    specPerson.Interests = "Physics";

    // super person
    SuperPerson supPerson = new SuperPerson();
    supPerson.ID = "2";
    supPerson.Name = "Superman";
    supPerson.Alias = "Clark Kent";
    supPerson.City = "Metropolis";
    supPerson.Age = int.MaxValue;
    supPerson.Skills.Add("fly");
    supPerson.Skills.Add("strong");

    // Add Persons
    personen.AddPerson(normPerson);
    personen.AddPerson(specPerson);
    personen.AddPerson(supPerson);

    // Serialize 
    Type[] personTypes = { typeof(Person), typeof(SpecialPerson), typeof(SuperPerson) };
    XmlSerializer serializer = new XmlSerializer(typeof(PersonalList), personTypes); 
    FileStream fs = new FileStream("Personenliste.xml", FileMode.Create); 
    serializer.Serialize(fs, personen); 
    fs.Close(); 
    personen = null;

    // Deserialize 
    fs = new FileStream("Personenliste.xml", FileMode.Open); 
    personen = (PersonalList)serializer.Deserialize(fs); 
    serializer.Serialize(Console.Out, personen);
    Console.ReadLine();
}

Wichtig ist die Definition und enthält die verschiedenen Typen.

Damasch
quelle
22

Siehe Einführung in die XML-Serialisierung :

Elemente, die serialisiert werden können

Die folgenden Elemente können mit der XmlSerializer- Klasse serialisiert werden :

  • Öffentliche Lese- / Schreibeigenschaften und Felder öffentlicher Klassen
  • Klassen, die ICollectionoder implementierenIEnumerable
  • XmlElement Objekte
  • XmlNode Objekte
  • DataSet Objekte

Insbesondere spielt ISerializabledas [Serializable]Attribut keine Rolle.


Nachdem Sie uns nun mitgeteilt haben, was Ihr Problem ist ("es funktioniert nicht" ist keine Problemstellung), können Sie Antworten auf Ihr tatsächliches Problem erhalten, anstatt Vermutungen anzustellen.

Wenn Sie eine Sammlung eines Typs serialisieren, aber tatsächlich eine Sammlung von Instanzen abgeleiteter Typen serialisieren, müssen Sie dem Serializer mitteilen, welche Typen Sie tatsächlich serialisieren werden. Dies gilt auch für Sammlungen von object.

Sie müssen den XmlSerializer-Konstruktor (Type, Type []) verwenden, um die Liste der möglichen Typen anzugeben .

John Saunders
quelle
msdn.microsoft.com/en-us/library/182eeyhh%28VS.85%29.aspx - Link wurde verstümmelt, danke dafür verstehe ich mehr ... viel zu lesen.
Simon D
2
Vielen Dank auch für den Rat, wie man Fragen stellt, es wird geschätzt.
Simon D
+1. Ja, sie spielen für XML keine Rolle, aber wenn er jemals andere Arten der Serialisierung verwenden möchte, werden sie es tun. Da er auf ISerializable umstellte, schien es für ihn eine vollkommen grundlegende Frage zu sein, zu überprüfen, ob die verschiedenen Typen diese Schnittstelle implementiert hatten. Ich hoffe, es war keine Auseinandersetzung mit meinem Kommentar, und ich denke, dies war hilfreich, um einige dieser Details zu klären.
Erich Mirabal
1
Ich habe nur gesagt, dass es einen Unterschied gibt, ein Objekt serialisierbar aufzurufen und in XML zu serialisieren. Ich habe es nicht zu persönlich oder einfühlsam genommen; Ich habe Ihre Antwort tatsächlich positiv bewertet, da sie einige gute Punkte zur Serialisierung in XML hervorgebracht hat, die nie erwähnt werden. Für XML sind sie möglicherweise nicht erforderlich. In der Regel empfiehlt es sich jedoch, andere Serialisierungen problemlos zu unterstützen.
Erich Mirabal
1
@Erich: Ich muss nicht zustimmen. Die verschiedenen Serialisierungstechnologien sind zu unterschiedlich. Es macht keinen Sinn, diejenigen zu unterstützen, die Sie nicht verwenden werden. Bedenken Sie: Hatten Sie vor, diejenigen zu testen, die Sie nicht verwenden? Meiner Meinung nach funktioniert es nicht, wenn Sie es nicht getestet haben. Warum Code erstellen, von dem Sie wissen, dass Sie ihn nicht testen werden?
John Saunders
6

Sie können eine Sammlung von Objekten nicht serialisieren, ohne die erwarteten Typen anzugeben. Sie müssen die Liste der erwarteten Typen an den Konstruktor von XmlSerializer(dem extraTypesParameter) übergeben:

List<object> list = new List<object>();
list.Add(new Foo());
list.Add(new Bar());

XmlSerializer xs = new XmlSerializer(typeof(object), new Type[] {typeof(Foo), typeof(Bar)});
using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xs.Serialize(streamWriter, list);
}

Wenn alle Objekte Ihrer Liste von derselben Klasse erben, können Sie das XmlIncludeAttribut auch verwenden , um die erwarteten Typen anzugeben:

[XmlInclude(typeof(Foo)), XmlInclude(typeof(Bar))]
public class MyBaseClass
{
}
Thomas Levesque
quelle
Hmm, das sieht interessant aus. Ich gehe davon aus, dass es immer noch nicht deserialisiert wird?
Ian
Es wird deserialisieren. Tatsächlich wird während der Serialisierung ein zusätzliches Attribut xsi:typeauf das Element geschrieben, das das Objekt darstellt. Dies ermöglicht XmlSerializer, den tatsächlichen Typ des Objekts zu kennen
Thomas Levesque
1
+1 Tolle Antwort, die einzige, die die Frage beantwortet, soweit ich sehen kann. Basierend auf meinen eigenen Tests sollte der erste Typ, den Sie an den XmlSerializer-Ctor übergeben, List <Objekt> und nicht Objekt sein, wie bearbeitet.
JWG
Wie würden Sie dies tun, wenn Sie nicht alle Typen kennen würden, die von der Basisklasse abgeleitet sind? Zum Beispiel schreibe ich ein Plugin-Framework mit einer Basisklasse, die von Drittanbietern erweitert wird.
Dasith Wijes
@Dasiths, können Sie die Typen zur Laufzeit entdecken? Wenn Sie können, versuchen Sie , die Typen mithilfe von XmlAttributeOverrides dynamisch hinzuzufügen.
Thomas Levesque
4

Ich denke, es ist am besten, wenn Sie Methoden mit generischen Argumenten verwenden, wie die folgenden:

public static void SerializeToXml<T>(T obj, string fileName)
{
    using (var fileStream = new FileStream(fileName, FileMode.Create))
    { 
        var ser = new XmlSerializer(typeof(T)); 
        ser.Serialize(fileStream, obj);
    }
}

public static T DeserializeFromXml<T>(string xml)
{
    T result;
    var ser = new XmlSerializer(typeof(T));
    using (var tr = new StringReader(xml))
    {
        result = (T)ser.Deserialize(tr);
    }
    return result;
}
Andreas Grech
quelle
OK vielleicht, aber das macht immer noch nicht das, was ich will - serialisiere eine Liste von Objekten, möglicherweise von verschiedenen Typen. Ich möchte in der Lage sein, eine Reihe von Objekten, die ich speichern möchte, zu einer Liste hinzuzufügen und dann die gesamte Liste auf einmal (und in eine Datei) zu serialisieren.
Simon D
1
Paradisonoir, deren Konstruktion temporäre Serialisierungsassemblierungen für den angegebenen Typ (und alle referenzierten Typen, die deserialisiert werden müssen) erstellt. Wenn Sie ein großes Objekt-Graoh haben, das serialisiert werden muss, oder nur eine große Anzahl von Objekten, und Sie XmlSerializer wiederholt aufrufen, kann dies langsam sein. Wir haben festgestellt, dass das Serialisieren und Deserialisieren einiger Einstellungen 10 bis 13 Sekunden dauerte. Dies wurde fast augenblicklich durch die Vorerstellung dieser Serialisierungsassemblys vorgenommen.
Ian
1
@Fred Der XmlSerializer (Type) -Konstruktor erstellt eine temporäre Assembly, um die Serialisierung für diesen Typ zu verarbeiten, und fügt sie der AppDomain hinzu. Wenn Sie sie jedoch ein zweites Mal für denselben Typ aufrufen, wird dieselbe temporäre Assembly nicht erneut verwendet. es schafft einen anderen. Da Assemblys nach dem Laden nicht mehr aus der AppDomain entfernt werden können, erhöht sich Ihr Speicherbedarf jedes Mal geringfügig, wenn Sie den Konstruktor aufrufen.
Rex M
1
Wenn in einer Anwendung wie der, an der ich täglich arbeite, jede neue temporäre Baugruppe 1 KB darstellt, würden 200 Gigabyte pro Tag verloren gehen. Durch sorgfältiges Verwalten der XmlSerializer in einem Typ-Cache behalten wir denselben Satz von Serializern im Speicher bei, wobei die Gesamtkosten etwa 1 Megabyte Cache betragen.
Rex M
2
@RexM - Ich weiß, dass dies ein alter Beitrag ist, daher haben sich die Dinge möglicherweise geändert, aber ich dachte, dass die XmlSerializer(Type)Überlastung einer der sicher zu verwendenden Konstruktoren ist, da sie die zwischengespeicherten Assemblys wiederverwendet. (der andere ist öffentlich XmlSerializer(Type type, string defaultNamespace)) support.microsoft.com/kb/886385/en-us
keyboardP
3

Ich denke, Dreas Ansatz ist in Ordnung. Eine Alternative dazu besteht jedoch darin, einige statische Hilfsmethoden zu haben und IXmlSerializable auf jeder Ihrer Methoden zu implementieren, z. B. eine XmlWriter-Erweiterungsmethode und die XmlReader-Methode, um sie zurückzulesen.

public static void SaveXmlSerialiableElement<T>(this XmlWriter writer, String elementName, T element) where T : IXmlSerializable
{
   writer.WriteStartElement(elementName);
   writer.WriteAttributeString("TYPE", element.GetType().AssemblyQualifiedName);
   element.WriteXml(writer);
   writer.WriteEndElement();
}

public static T ReadXmlSerializableElement<T>(this XmlReader reader, String elementName) where T : IXmlSerializable
{
   reader.ReadToElement(elementName);

   Type elementType = Type.GetType(reader.GetAttribute("TYPE"));
   T element = (T)Activator.CreateInstance(elementType);
   element.ReadXml(reader);
   return element;
}

Wenn Sie die XmlSerializer-Klasse direkt verwenden, erstellen Sie nach Möglichkeit vorab Serialisierungsassemblys, da Sie beim regelmäßigen Erstellen neuer XmlSerializer einen großen Leistungseinbruch erzielen können.

Für eine Sammlung benötigen Sie ungefähr Folgendes:

public static void SaveXmlSerialiazbleCollection<T>(this XmlWriter writer, String collectionName, String elementName, IEnumerable<T> items) where T : IXmlSerializable
{
   writer.WriteStartElement(collectionName);
   foreach (T item in items)
   {
      writer.WriteStartElement(elementName);
      writer.WriteAttributeString("TYPE", item.GetType().AssemblyQualifiedName);
      item.WriteXml(writer);
      writer.WriteEndElement();
   }
   writer.WriteEndElement();
}
Ian
quelle
Das sieht interessant aus, aber ich bin mir nicht sicher, wie ich es genau verwenden soll. Ich werde versuchen, ein bisschen damit zu spielen und zu sehen, ob ich es zum Laufen bringen kann.
Simon D
Ok, zunächst müssen alle Objekte in der Sammlung stark auf denselben Basistyp typisiert werden. Die Objekte müssen IXmlSerializable implementieren (nicht zu schwer). Für eine Sammlung, z. B. List <T>, rufen Sie dann Folgendes auf: XmlWriter.SaveXmlSerializableCollection ("item", this.collectionOfTs)
Ian
Übrigens, müssen Sie sie auch deserialisieren? Wenn nicht, kann ich Ihnen wahrscheinlich ein ausführlicheres Beispiel geben ...
Ian
Ich werde wahrscheinlich deserialisieren, aber das ist schon ein gutes Stück, um damit fertig zu werden. Ich werde versuchen, das, was du mir gegeben hast, bereits in die Praxis umzusetzen. Vielen Dank.
Simon D
2

Unten ist eine Util-Klasse in meinem Projekt:

namespace Utils
{
    public static class SerializeUtil
    {
        public static void SerializeToFormatter<F>(object obj, string path) where F : IFormatter, new()
        {
            if (obj == null)
            {
                throw new NullReferenceException("obj Cannot be Null.");
            }

            if (obj.GetType().IsSerializable == false)
            {
                //  throw new 
            }
            IFormatter f = new F();
            SerializeToFormatter(obj, path, f);
        }

        public static T DeserializeFromFormatter<T, F>(string path) where F : IFormatter, new()
        {
            T t;
            IFormatter f = new F();
            using (FileStream fs = File.OpenRead(path))
            {
                t = (T)f.Deserialize(fs);
            }
            return t;
        }

        public static void SerializeToXML<T>(string path, object obj)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (FileStream fs = File.Create(path))
            {
                xs.Serialize(fs, obj);
            }
        }

        public static T DeserializeFromXML<T>(string path)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            using (FileStream fs = File.OpenRead(path))
            {
                return (T)xs.Deserialize(fs);
            }
        }

        public static T DeserializeFromXml<T>(string xml)
        {
            T result;

            var ser = new XmlSerializer(typeof(T));
            using (var tr = new StringReader(xml))
            {
                result = (T)ser.Deserialize(tr);
            }
            return result;
        }


        private static void SerializeToFormatter(object obj, string path, IFormatter formatter)
        {
            using (FileStream fs = File.Create(path))
            {
                formatter.Serialize(fs, obj);
            }
        }
    }
}
ligaoren
quelle
1
Die Verwendung von IFormatterist als Antwort auf eine Frage zur XML-Serialisierung verwirrend. Außerdem sollten Sie werfen ArgumentNullExeption, nicht NullReferenceException. Du solltest niemals werfen NullReferenceException.
John Saunders
2

Der einfachste Weg, den ich gefunden habe. Wenden Sie das System.Xml.Serialization.XmlArrayAttribut darauf an.

[System.Xml.Serialization.XmlArray] //This is the part that makes it work
List<object> serializableList = new List<object>();

XmlSerializer xmlSerializer = new XmlSerializer(serializableList.GetType());

serializableList.Add(PersonList);

using (StreamWriter streamWriter = System.IO.File.CreateText(fileName))
{
    xmlSerializer.Serialize(streamWriter, serializableList);
}

Der Serializer erkennt, dass es sich um ein Array handelt, und serialisiert die Elemente der Liste als untergeordnete Knoten.

Lee
quelle
0

Mit dem Parameter knowTypeList können mit DataContractSerializer mehrere bekannte Typen serialisiert werden:

private static void WriteObject(
        string fileName, IEnumerable<Vehichle> reflectedInstances, List<Type> knownTypeList)
    {
        using (FileStream writer = new FileStream(fileName, FileMode.Append))
        {
            foreach (var item in reflectedInstances)
            {
                var serializer = new DataContractSerializer(typeof(Vehichle), knownTypeList);
                serializer.WriteObject(writer, item);
            }
        }
    }
Sharunas Bielskis
quelle
0

Wenn die XML-Ausgabeanforderung geändert werden kann, können Sie immer die binäre Serialisierung verwenden, die besser für die Arbeit mit heterogenen Objektlisten geeignet ist. Hier ist ein Beispiel:

private void SerializeList(List<Object> Targets, string TargetPath)
{
    IFormatter Formatter = new BinaryFormatter();

    using (FileStream OutputStream = System.IO.File.Create(TargetPath))
    {
        try
        {
            Formatter.Serialize(OutputStream, Targets);
        } catch (SerializationException ex) {
            //(Likely Failed to Mark Type as Serializable)
            //...
        }
}

Verwendung als solche:

[Serializable]
public class Animal
{
    public string Home { get; set; }
}

[Serializable]
public class Person
{
    public string Name { get; set; }
}


public void ExampleUsage() {

    List<Object> SerializeMeBaby = new List<Object> {
        new Animal { Home = "London, UK" },
        new Person { Name = "Skittles" }
    };

    string TargetPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "Test1.dat");

    SerializeList(SerializeMeBaby, TargetPath);
}
Robert Venables
quelle
Schönes Beispiel. Dies ist mehr, was ich dachte - eine Reihe von verschiedenen Objekten und ich möchte sie in einer Datei zusammenfassen. Ich bevorzuge XML gegenüber Binär, werde es aber versuchen.
Simon D