XML可序列化可序列化对象的通用列表


73

我可以序列化可序列化对象的通用列表,而无需指定它们的类型。

类似于下面的破损代码背后的意图:

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

编辑:

对于那些想了解细节的人:当我尝试运行此代码时,它在XMLSerializer [...]行上的错误如下:

无法序列化接口System.Runtime.Serialization.ISerializable。

如果我改变了List<object>我会得到"There was an error generating the XML document."。InnerException详细信息是"{"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."}"

人员对象定义如下:

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

PersonList只是一个List<Person>

不过,这只是用于测试,因此并不认为细节太重要。关键是我有一个或多个不同的对象,所有这些对象都是可序列化的。我想将它们全部序列化为一个文件。我认为最简单的方法是将它们放在通用列表中,然后一次性序列化列表。但这是行不通的。

我也尝试过List<IXmlSerializable>,但是失败了

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

抱歉,缺少详细信息,但是我是对此的初学者,不知道需要什么详细信息。如果人们要求更多细节,而试图以某种方式让我理解需要的细节或概述可能指示的基本答案,那将很有帮助。

也要感谢到目前为止我得到的两个答案-我可能会花更多的时间阅读而没有得到这些想法。人们在此站点上的帮助程度令人惊讶。


1
我已经尝试过了,但是没有用,我想不出如何使它起作用。我尝试将其更改为List <object>,但该方法也不起作用。
西蒙D

它以什么方式“不起作用”?如果需要答案,请提供详细信息。
约翰·桑德斯

你有什么错误?所有对象都实现ISerializable还是仅具有[Serializable]属性?
Erich Mirabal,2009年

约翰:上面的示例(适用于List <Object>时)将产生一个异常,解释特定类型不是预期的。
罗伯特·韦纳布尔斯2009年

无参数的构造函数错误是因为您要序列化的类没有不带参数的构造。在每个类上定义一个私有的<ClassName>(){},它会消失。
伊恩(Ian)2009年

Answers:


81

我有一个带有动态绑定项的通用List <>的解决方案。

类PersonalList是根元素

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

类Person这是一个列表元素

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

类SpecialPerson继承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;
    }
}

类SuperPerson继承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;
    }
}

和主要的测试源

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

重要的是定义,并包括不同的类型。


22

请参阅XML序列化简介

可以序列化的项目

可以使用XmlSerializer 类对以下各项进行序列化:

  • 公共读/写属性和公共类的字段
  • 实现ICollectionIEnumerable
  • XmlElement 对象
  • XmlNode 对象
  • DataSet 对象

尤其ISerializable[Serializable]属性还是没关系。


既然您已经告诉我们您的问题是什么(“不起作用”不是问题说明),那么您可以获得实际问题的答案,而不是猜测。

当序列化一个类型的集合但实际上将序列化派生类型的实例的集合时,您需要让序列化器知道您将实际序列化的类型。对的集合也是如此object

您需要使用XmlSerializer(Type,Type [])构造函数来列出可能的类型。


msdn.microsoft.com/zh-cn/library/182eeyhh%28VS.85%29.aspx-链接被弄乱了,谢谢,我了解更多...很多东西可供阅读。
西蒙D 2009年

2
也感谢您提出问题的建议,我们对此表示赞赏。
西蒙D 2009年

+1。是的,它们对于XML无关紧要,但是,如果他想使用其他类型的序列化,它们将会。而且,由于他正在转换为ISerializable,所以对于他来说检查不同类型实现该接口似乎是一个完美的基本问题。我希望这不是我的评论,我认为这有助于澄清其中的一些细节。
Erich Mirabal,2009年

1
我只是说,调用可序列化的对象并以XML对其进行序列化是有区别的。我个人并不太敏感。我实际上投票赞成您的答案,因为它带来了一些有关XML序列化的好点,但从未提到过。尽管如此,XML可能并不需要它们,但这通常是一个好习惯,这样您就可以轻松支持其他序列化。
Erich Mirabal,2009年

1
@Erich:我必须不同意。各种序列化技术太不同了。支持那些不使用的对象没有任何意义。考虑:您是否打算测试未使用的产品?我认为,如果您没有对其进行测试,那么它将无法正常工作。为什么生成您知道将不进行测试的代码?
约翰·桑德斯

6

如果不指定期望的类型,则无法序列化对象的集合。您必须将期望类型的列表传递给XmlSerializerextraTypes参数)的构造函数:

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

如果列表中的所有对象都继承自同一类,则还可以使用XmlInclude属性指定所需的类型:

[XmlInclude(typeof(Foo)), XmlInclude(typeof(Bar))]
public class MyBaseClass
{
}

嗯,这看起来很有趣。我以为它仍然不会反序列化?
伊恩

反序列化。实际上,在序列化期间,xsi:type会在表示对象的元素上写入一个额外的属性。这使XmlSerializer可以知道对象的实际类型
Thomas Levesque 2009年

1
+1个好答案,据我所知,唯一回答这个问题的人。根据我自己的测试,您传递给XmlSerializer ctor的第一种类型应该是List <object>而不是对象,正如编辑的那样。
jwg 2013年

如果您不知道从基类派生的所有类型,该怎么办?例如,我正在编写一个插件框架,该框架的基类将由第三方扩展。
Dasith Wijes,

@Dasiths,您可以在运行时发现类型吗?如果可以,请尝试使用XmlAttributeOverrides动态添加类型。
Thomas Levesque

4

我认为最好使用带有通用参数的方法,例如:

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

也许可以,但是仍然不能满足我的要求-序列化对象列表,可能是不同类型的对象。我希望能够将一堆要保存的对象添加到列表中,然后一次性将整个列表序列化(并保存到一个文件中)。
西蒙D 2009年

1
矛盾的是,它们的构造会为给定类型(以及需要反序列化的所有引用类型)创建临时序列化程序集。如果您有一个需要序列化的大对象graoh,或者只是大量对象,并且您反复调用XmlSerializer,则速度可能会很慢。我们发现序列化和反序列化某些设置需要10到13秒,这是通过预先构建这些序列化程序集而几乎立即实现的。
伊恩(Ian)2009年

1
@Fred XmlSerializer(Type)构造函数创建一个临时程序集以处理该类型的序列化,并将其添加到AppDomain中,但是如果您再次调用同一类型,则不会重复使用相同的临时程序集,它创建了另一个。由于程序集一旦加载就无法从AppDomain中删除,因此每次调用构造函数时,内存占用量都会略有增加。
Rex M

1
在我每天处理的应用程序中,如果每个新的临时程序集代表1kb,我们每天将泄漏200 GB。通过仔细管理类型缓存中的XmlSerializer,我们将相同的序列化器集保留在内存中,总成本约为1兆字节。
Rex M

2
@RexM-我知道这是一个过时的文章,所以事情可能有所改变,但是我认为XmlSerializer(Type)重载是使用构造函数的安全方法之一,因为它确实重用了缓存的程序集?(另一个是公开的XmlSerializer(Type type, string defaultNamespace)support.microsoft.com/kb/886385/en-us
keyboardP

3

我认为Dreas的方法还可以。但是,对此的一种替代方法是具有一些静态帮助器方法,并在每个方法上实现IXmlSerializable,例如XmlWriter扩展方法和XmlReader以将其读回。

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

如果确实采用了直接使用XmlSerializer类的方法,请在可能的情况下事先创建序列化程序集,因为在定期构造新的XmlSerializer时会受到很大的性能影响。

对于集合,您需要这样的东西:

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

这看起来很有趣,但是我不确定如何正确使用它。我将尝试使用它,看看是否可以使它工作。
西蒙D

好的,首先需要将集合中的所有对象强类型化为相同的基本类型。这些对象需要实现IXmlSerializable(不太难)。对于收集如列表<T>你然后调用类似:XmlWriter.SaveXmlSerializableCollection(“项目”,this.collectionOfTs)
伊恩·

顺便说一句,您是否也需要反序列化它们?如果不是这样,我可能可以为您提供更完整的例子……
Ian 2009年

我可能会反序列化,但这已经足够了。我会尽力将您已经给我的东西付诸实践。非常感谢。
西蒙D

2

下面是我的项目中的Util类:

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

1
使用的IFormatter是混乱的回答为对XML序列化的问题。另外,您应该投掷ArgumentNullExeption,而不是NullReferenceException。你不应该扔NullReferenceException
约翰·桑德斯

2

我发现了最简单的方法。将System.Xml.Serialization.XmlArray属性应用于它。

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

序列化器将以阵列形式接收并将序列项作为子节点序列化。


0

knowTypeList参数可让DataContractSerializer序列化几种已知类型:

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

0

如果可以更改XML输出要求,那么您始终可以使用二进制序列化-更适合使用异构对象列表。这是一个例子:

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)
            //...
        }
}

这样使用:

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

很好的例子。这更多的是我的想法-许多不同的对象,我想将它们放入一个文件中。我更喜欢XML而不是二进制文件,但是会尝试这样做。
西蒙D 2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.