XML Serialize общий список сериализуемых объектов
Могу ли я сериализовать общий список сериализуемых объектов без указания их типа.
Что-то вроде намерения за сломанный код ниже:
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."}"
Объект person определяется следующим образом:
[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.
Извините за отсутствие деталей, но я новичок в этом и не знаю, какие детали требуются. Было бы полезно, если бы люди, спрашивающие о более подробной информации, попытались ответить таким образом, который позволил бы мне понять, какие детали требуются, или дать базовый ответ с указанием возможных направлений.
Также благодаря двум ответам, которые я получил до сих пор - я мог бы потратить гораздо больше времени на чтение, не понимая этих идей. Удивительно, насколько полезны люди на этом сайте.
10 ответов
У меня есть решение для общего списка<> с динамическими связанными элементами.
Класс 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();
}
Важным является определение и включает различные типы.
См. Введение в сериализацию XML:
Предметы, которые можно сериализовать
Следующие элементы могут быть сериализованы с использованием класса XmlSerializer:
- Открытые свойства чтения / записи и поля открытых классов
- Классы, которые реализуют
ICollection
или жеIEnumerable
XmlElement
объектыXmlNode
объектыDataSet
объекты
Особенно, ISerializable
или [Serializable]
атрибут не имеет значения.
Теперь, когда вы сказали нам, в чем заключается ваша проблема ("она не работает" - это не постановка проблемы), вы можете получить ответы на свои настоящие проблемы вместо предположений.
Когда вы сериализуете коллекцию типа, но на самом деле сериализуете коллекцию экземпляров производных типов, вы должны сообщить сериализатору, какие типы вы на самом деле сериализуете. Это также верно для коллекций object
,
Вам нужно использовать конструктор XmlSerializer(Type,Type[]), чтобы получить список возможных типов.
Вы не можете сериализовать коллекцию объектов без указания ожидаемых типов. Вы должны передать список ожидаемых типов в конструктор XmlSerializer
(extraTypes
параметр):
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
{
}
Я думаю, что лучше всего использовать методы с общими аргументами, например:
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;
}
Я думаю, что подход Дреаса в порядке. Однако альтернативой этому является использование некоторых статических вспомогательных методов и реализация 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();
}
Самый простой способ сделать это, который я нашел.. Применить 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);
}
Сериализатор определит, что это массив, и сериализует элементы списка как дочерние узлы.
Ниже приведен класс 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);
}
}
}
}
Пусть Serialize и Deserialize будут списками безымянных объектов в C#:
// create a list of Shapes to serialize.
List<Shape> listOfShapes = new()
{
new Circle { Colour = "Red", Radius = 2.5 },
new Rectangle { Colour = "Blue", Height = 20.0, Width = 10.0 },
new Circle { Colour = "Green", Radius = 8.0 },
new Circle { Colour = "Purple", Radius = 12.3 },
new Rectangle { Colour = "Blue", Height = 45.0, Width = 18.0 }
};
string filepath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Testfile.xml");
//Serialize
List<Type> types = new();
foreach(Shape s in listOfShapes)
{
types.Add(s.GetType());
}
XmlSerializer serializerXml = new XmlSerializer(listOfShapes.GetType(),types.ToArray());
FileStream fsxml = File.Create(filepath);
serializerXml.Serialize(fsxml, listOfShapes);
fsxml.Close();
//Deserialize.
FileStream fsxmlr= File.Open(filepath, FileMode.Open);
List<Shape> loadedShapesXml = serializerXml.Deserialize(fsxmlr) as List<Shape>;
foreach (Shape item in loadedShapesXml)
{
WriteLine("{0} is {1} and has an area of {2:N2}",
item.GetType().Name, item.Colour, item.Area);
}
fsxmlr.Close();
Если требование вывода 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);
}
Параметр 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);
}
}
}