I have the following test XML string:
b1
a2
No, basically; XmlSerializer
doesn't support that. If you want to use that option you'd need to write it manually with XDocument
or XmlDocument
or XmlReader
.
You can do this with XmlSerializer
by using a single collection to capture both the <a>
and <b>
elements and applying the [XmlElement(Name, Type = typeof(...))] attribute to it multiple times, once for each desired element name. Because you are using a single collection to deserialize both elements, the order is automatically preserved. However, to make this work, XmlSerializer
must be able to determine the correct element name when re-serializing. There are two approaches to accomplish this, as documented in Choice Element Binding Support:
If the collection contains polymorphic items, the element name can be mapped to the concrete item type by using the [XmlElementAttribute(String, Type)] constructor. For instance, if you have a sequence of elements that might be strings or integers like so:
<Things>
<string>Hello</string>
<int>999</int>
</Things>
This can be bound to a collection as follows:
public class Things
{
[XmlElement(Type = typeof(string)),
XmlElement(Type = typeof(int))]
public List<object> StringsAndInts { get; set; }
}
If the collection contains only a single type of item, the element name can be encoded in an associated array of enum
values, where the enum
names correspond to the element names and the array itself is identified via the [XmlChoiceIdentifierAttribute] attribute.
For details, see the documentation examples.
I find option #1 easier to work with than option #2. Using this approach, the following model will deserialize and re-serialize your XML while successfully preserving the order of the <a>
and <b>
elements:
public abstract class StringElementBase
{
[XmlText]
public string Text { get; set; }
public static implicit operator string(StringElementBase element)
{
return element == null ? null : element.Text;
}
}
public sealed class A : StringElementBase
{
}
public sealed class B : StringElementBase
{
}
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement("a", Type = typeof(A))]
[XmlElement("b", Type = typeof(B))]
public List<StringElementBase> Items { get; } = new List<StringElementBase>();
[XmlIgnore]
// For convenience, enumerate through the string values of the items.
public IEnumerable<string> ItemValues { get { return Items.Select(i => (string)i); } }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
Working .Net fiddle.
For more examples of using [XmlChoiceIdentifier]
to deserialize a sequence of elements with different names to c# objects of the same type, see for instance here or here.