См. Ниже код, который записывает XML-файл, чтобы записать простой класс, содержащий список из трех объектов. 3 объекта в списке сходятся друг от друга: Base, Derived1, Derived2. Я использую XMLArrayItemAttributes для переопределения имен во время сериализации. Это отлично работает в .NET 3.0, но теперь выводит другой результат в .NET 4.0. См. Ниже выходы, отмечая, в частности, второй элемент потомка DerivedItem2.XML-сериализация - отличный результат в .NET 4.0
У кого-нибудь есть опыт работы с этим и как я могу исправить его для работы в .NET 4.0, как это было в версии 3.5?
Кажется, что я не могу контролировать порядок, в котором элементы массива переопределены. Это не похоже на порядок, в котором они добавляются в XMLArrayItems.
Редактирование: Я только что попробовал тот же пример, используя MONO для фреймворков версии 4.0 и 4.5, и он отлично работает с ними. Может ли это быть ошибкой с версиями рамок Microsoft?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
TestGroup g = new TestGroup();
XmlSerializer s = new XmlSerializer(typeof(TestGroup), g.GetOverrides());
TextWriter w = new StreamWriter("c:\\#\\test.xml");
s.Serialize(w, g);
w.Close();
}
}
public class TestGroup
{
public List<BaseItem> Items { get; set; }
public TestGroup()
{
Items = new List<BaseItem>();
BaseItem b = new BaseItem();
b.BaseName = "Base Name";
Items.Add(b);
DerivedItem d1 = new DerivedItem();
d1.BaseName = "D1";
d1.DerivedName = "D1";
Items.Add(d1);
DerivedItem2 d2 = new DerivedItem2();
d2.BaseName = "D2";
//d2.DerivedName = "D2";
d2.Derived2Name = "D2";
Items.Add(d2);
}
public XmlAttributeOverrides GetOverrides()
{
XmlAttributes atts = new XmlAttributes();
for (int i = 0; i < Items.Count; i++)
{
BaseItem b = Items[i];
Type ItemType = b.GetType();
XmlArrayItemAttribute ItemAtt = new XmlArrayItemAttribute();
ItemAtt.ElementName = ItemType.Name;
ItemAtt.Type = ItemType;
atts.XmlArrayItems.Add(ItemAtt);
}
XmlAttributeOverrides attOvers = new XmlAttributeOverrides();
attOvers.Add(typeof(TestGroup), "Items", atts);
return attOvers;
}
}
public class BaseItem : IXmlSerializable
{
public string BaseName;
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
// not required for example
}
public virtual void WriteXml(XmlWriter writer)
{
writer.WriteElementString("BaseName", this.BaseName);
}
}
public class DerivedItem: BaseItem
{
public string DerivedName;
public override void WriteXml(XmlWriter writer)
{
base.WriteXml(writer);
writer.WriteElementString("DerivedName", this.DerivedName);
}
}
public class DerivedItem2: DerivedItem
{
public string Derived2Name;
public override void WriteXml(XmlWriter writer)
{
base.WriteXml(writer);
writer.WriteElementString("Derived2Name", this.Derived2Name);
}
}
Выход в исходном (.NET 3.0):
<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<BaseItem>
<BaseName>Base Name</BaseName>
</BaseItem>
<DerivedItem>
<BaseName>D1</BaseName>
<DerivedName>D1</DerivedName>
</DerivedItem>
<DerivedItem2>
<BaseName>D2</BaseName>
<DerivedName />
<Derived2Name>D2</Derived2Name>
</DerivedItem2>
</Items>
</TestGroup>
выход изменен (.NET 4.0):
<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<BaseItem>
<BaseName>Base Name</BaseName>
</BaseItem>
<DerivedItem>
<BaseName>D1</BaseName>
<DerivedName>D1</DerivedName>
</DerivedItem>
<DerivedItem>
<BaseName>D2</BaseName>
<DerivedName />
<Derived2Name>D2</Derived2Name>
</DerivedItem>
</Items>
</TestGroup>
Update: Выход из .NET 4,5
<?xml version="1.0" encoding="utf-8"?>
<TestGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<BaseItem>
<BaseName>Base Name</BaseName>
</BaseItem>
<BaseItem>
<BaseName>D1</BaseName>
<DerivedName>D1</DerivedName>
</BaseItem>
<DerivedItem2>
<BaseName>D2</BaseName>
<DerivedName />
<Derived2Name>D2</Derived2Name>
</DerivedItem2>
</Items>
</TestGroup>
Обновление: Превращение o n отладочный переключатель в app.config, как показано ниже, ссылается на http://msdn.microsoft.com/en-us/library/aa302290.aspx, я обнаружил, что порядок, в котором применяется Serialization, переопределяет отличие от порядка заполнения массива переопределения. Кто-нибудь знает, как этот порядок определяется или переопределяется?
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<switches>
<add name="XmlSerialization.Compilation" value="4" />
</switches>
</system.diagnostics>
</configuration>
Это дает мне C# выходной файл, который показывает порядок переопределения некорректным:
void Write2_TestGroup(string n, string ns, global::WindowsFormsApplication1.TestGroup o, bool isNullable, bool needType) {
if ((object)o == null) {
if (isNullable) WriteNullTagLiteral(n, ns);
return;
}
if (!needType) {
System.Type t = o.GetType();
if (t == typeof(global::WindowsFormsApplication1.TestGroup)) {
}
else {
throw CreateUnknownTypeException(o);
}
}
WriteStartElement(n, ns, o, false, null);
if (needType) WriteXsiType(@"TestGroup", @"");
{
global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem> a = (global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem>)((global::System.Collections.Generic.List<global::WindowsFormsApplication1.BaseItem>)[email protected]);
if (a != null){
WriteStartElement(@"Items", @"", null, false);
for (int ia = 0; ia < ((System.Collections.ICollection)a).Count; ia++) {
global::WindowsFormsApplication1.BaseItem ai = (global::WindowsFormsApplication1.BaseItem)a[ia];
if ((object)(ai) != null){
if (ai is global::WindowsFormsApplication1.DerivedItem) {
WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.DerivedItem)ai), @"DerivedItem", @"", true, true);
}
else if (ai is global::WindowsFormsApplication1.BaseItem) {
WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.BaseItem)ai), @"BaseItem", @"", true, true);
}
else if (ai is global::WindowsFormsApplication1.DerivedItem2) {
WriteSerializable((System.Xml.Serialization.IXmlSerializable)((global::WindowsFormsApplication1.DerivedItem2)ai), @"DerivedItem2", @"", true, true);
}
else if ((object)(ai) != null){
throw CreateUnknownTypeException(ai);
}
}
}
WriteEndElement();
}
}
WriteEndElement(o);
}
Да, это кажется странным. Вы пробовали с .net 4.5? Согласно моему мнению, порядок, на который проверяется тип 'ai', должен быть только от самого специализированного до наиболее обобщенного типа. Все остальное не имеет смысла, поскольку оно никогда не выдаст правильное имя типа в прилагаемом XML, и поэтому десериализация не будет работать. Вы пытались десериализовать созданный XML? Вы получаете то же поведение, если не переопределяете WriteXml()? – Rory
Может быть какой-то фиктивный аспект для сериализатора, например. вы пытались обеспечить, чтобы ваши производные классы имели метод ReadXml()? – Rory
Спасибо за комментарии Рори. Я пробовал несколько комбинаций включения/исключения ReadXml/WriteXml и, похоже, все же дает одинаковые имена базовых элементов. Также попробовал это с .NET 4.5 как в прежнем режиме, так и без изменений. –