能否将XML反序列化为List<T>?

168

给定以下XML:

<?xml version="1.0"?>
<user_list>
   <user>
      <id>1</id>
      <name>Joe</name>
   </user>
   <user>
      <id>2</id>
      <name>John</name>
   </user>
</user_list>

并且下面有这个类:

public class User {
   [XmlElement("id")]
   public Int32 Id { get; set; }

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

使用XmlSerializer将xml反序列化为List<User>是否可行?如果可以,我需要使用什么类型的附加属性,或者我需要使用哪些附加参数来构造XmlSerializer实例?

数组(User[])也可接受,但可能不太理想。

8个回答

154
你可以轻松地封装这个列表:
using System;
using System.Collections.Generic;
using System.Xml.Serialization;

[XmlRoot("user_list")]
public class UserList
{
    public UserList() {Items = new List<User>();}
    [XmlElement("user")]
    public List<User> Items {get;set;}
}
public class User
{
    [XmlElement("id")]
    public Int32 Id { get; set; }

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

static class Program
{
    static void Main()
    {
        XmlSerializer ser= new XmlSerializer(typeof(UserList));
        UserList list = new UserList();
        list.Items.Add(new User { Id = 1, Name = "abc"});
        list.Items.Add(new User { Id = 2, Name = "def"});
        list.Items.Add(new User { Id = 3, Name = "ghi"});
        ser.Serialize(Console.Out, list);
    }
}

5
使用[XmlElement("user")]是一个很好的解决方案,可以避免多余的元素级别。看到这个,我肯定认为它会发出<user>或<Items>节点(如果您没有使用XmlElement属性),然后在其下添加<user>节点。但我尝试了一下,并没有这样做,因此正好符合问题的要求。 - Jon Kragh
如果UserList下有两个列表怎么办?我尝试了你的方法,它说已经定义了一个名为XYZ且具有相同参数类型的成员。 - Kala J
2
我不知道为什么这被标记为正确答案。它包括添加一个类来包装列表。这显然是问题想要避免的。 - DDRider62
1
@DDRider62 这个问题并没有说“不包装”。大多数人都很实际,只想获取数据。这个答案允许您通过 .Items 成员来完成这一点。 - Marc Gravell

61

如果您使用XmlType修饰User类以匹配所需的大写格式:

[XmlType("user")]
public class User
{
   ...
}

那么在 XmlSerializer 构造函数中使用的 XmlRootAttribute 可以指定所需的根元素,并允许直接读取到 List<> 中:

    // e.g. my test to create a file
    using (var writer = new FileStream("users.xml", FileMode.Create))
    {
        XmlSerializer ser = new XmlSerializer(typeof(List<User>),  
            new XmlRootAttribute("user_list"));
        List<User> list = new List<User>();
        list.Add(new User { Id = 1, Name = "Joe" });
        list.Add(new User { Id = 2, Name = "John" });
        list.Add(new User { Id = 3, Name = "June" });
        ser.Serialize(writer, list);
    }

...

    // read file
    List<User> users;
    using (var reader = new StreamReader("users.xml"))
    {
        XmlSerializer deserializer = new XmlSerializer(typeof(List<User>),  
            new XmlRootAttribute("user_list"));
        users = (List<User>)deserializer.Deserialize(reader);
    }

来源:基于YK1答案


16
在我看来,这显然是这个问题的答案。这个问题是关于将内容反序列化为List<T>。除了可能有一个解决方案之外,所有其他解决方案都包括一个包含列表的包装类,而这显然不是发布的问题,也似乎是问题作者想要避免的。 - DDRider62
3
采用这种方法,必须静态缓存并重复使用 XmlSerializer 以避免严重的内存泄漏问题,请参见 Memory Leak using StreamReader and XmlSerializer 了解详细信息。 - dbc

18

是的,它可以对 List<> 进行序列化和反序列化。如果不确定,请确保使用 [XmlArray] 属性。

[Serializable]
public class A
{
    [XmlArray]
    public List<string> strings;
}

这适用于 Serialize() 和 Deserialize()。


那就是我缺失的魔法部分。 - NielW

15

我认为我已经找到了更好的方式。你不必将属性放入类中。我写了两个方法来进行序列化和反序列化,这些方法以通用列表作为参数。

看一下代码(对我有效):

private void SerializeParams<T>(XDocument doc, List<T> paramList)
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(paramList.GetType());

        System.Xml.XmlWriter writer = doc.CreateWriter();

        serializer.Serialize(writer, paramList);

        writer.Close();           
    }

private List<T> DeserializeParams<T>(XDocument doc)
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(List<T>));

        System.Xml.XmlReader reader = doc.CreateReader();

        List<T> result = (List<T>)serializer.Deserialize(reader);
        reader.Close();

        return result;
    }

因此,您可以序列化任何列表!您不需要每次指定列表类型。

        List<AssemblyBO> list = new List<AssemblyBO>();
        list.Add(new AssemblyBO());
        list.Add(new AssemblyBO() { DisplayName = "Try", Identifier = "243242" });
        XDocument doc = new XDocument();
        SerializeParams<T>(doc, list);
        List<AssemblyBO> newList = DeserializeParams<AssemblyBO>(doc);

3
感谢您实际回答问题。我想补充一点,对于 List<MyClass> ,文档元素应该命名为 ArrayOfMyClass - Max Toro

9
是的,它反序列化为List<>。不需要将其保存在数组中并包装/封装为列表。
public class UserHolder
{
    private List<User> users = null;

    public UserHolder()
    {
    }

    [XmlElement("user")]
    public List<User> Users
    {
        get { return users; }
        set { users = value; }
    }
}

反序列化代码。
XmlSerializer xs = new XmlSerializer(typeof(UserHolder));
UserHolder uh = (UserHolder)xs.Deserialize(new StringReader(str));

5

不确定 List<T> 是否可行,但数组确实可以。一些小技巧可以让将其转换为列表变得非常容易。

public class UserHolder {
   [XmlElement("list")]
   public User[] Users { get; set; }

   [XmlIgnore]
   public List<User> UserList { get { return new List<User>(Users); } }
}

2
可以不使用“holder”类吗? - Daniel Schaffer
@Daniel,据我所知,不行。您需要将其序列化和反序列化为某个具体的对象类型。我不认为XML序列化本身支持集合类作为序列化的起点。虽然我不是100%确定。 - JaredPar
[XmlElement("list")] 应该改为 [XmlArray("list")]。这是在 .NET 4.5 中反序列化工作的唯一方法。 - eduardobr

2

你好

XmlSerializer xs = new XmlSerializer(typeof(user[]));
using (Stream ins = File.Open(@"c:\some.xml", FileMode.Open))
foreach (user o in (user[])xs.Deserialize(ins))
   userList.Add(o);    

这不算特别花哨,但应该可行。


2
欢迎来到stackoverflow!为了提高帖子的准确性,最好为示例代码提供一个简短的描述 :) - Picrofo Software

-1

是的,您可以使用XmlSerializer反序列化为List<User>User[]。我更喜欢List<User>而不是User[]。请注意,XmlSerializer不支持将接口反序列化,因此您无法将其反序列化为ICollection<T>IReadOnlyCollection<T>IList<T>,否则会失败并抛出一个NotSupportedException异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接