问题
I am confused on how XmlSerializer works behind the scenes. I have a class that deserializes XML into an object. What I am seeing is for the following two elements that are NOT part of the Xml being deserialized.
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
}
Let's take the following XML as an example:
<MyClass>
<SomeNode>value</SomeNode>
</MyClass>
You notice that Tests and Comments are NOT part of the XML.
When this XML gets deserialized Comments is null(which is expected) and Tests is an empty list with a count of 0.
If someone could explain this to me it would be much appreciated. What I would prefer is that if <Tests>
is missing from the XML then the list should remain null, but if a (possibly empty) node <Tests />
is present then the list should get allocated.
回答1:
What you are observing is that members referring to modifiable collections such as List<T>
are automatically pre-allocated by XmlSerializer
at the beginning of deserialization. I am not aware of any place where this behavior is documented. It may be related to the behavior described in this answer to XML Deserialization of collection property with code defaults, which explains that, since XmlSerializer
supports adding to get-only and pre-allocated collections, if a pre-allocated collection contains default items then the deserialized items will be appended to it - possibly duplicating the contents. Microsoft may simply have chosen to pre-allocate all modifiable collections at the beginning of deserialization as the simplest way of implementing this.
The workaround from that answer, namely to use a surrogate array property, works here as well. Since an array cannot be appended to, XmlSerializer
must accumulate all the values and set them back when deserialization is finished. But if the relevant tag is never encountered, XmlSerializer
apparently does not begin accumulating values and so does not call the array setter. This seems to prevent the default pre-allocation of collections that you don't want:
[XmlRootAttribute("MyClass", Namespace = "", IsNullable = false)]
public class MyClass
{
private string comments;
public string Comments
{
set { comments = value; }
get { return comments; }
}
private System.Collections.Generic.List<string> tests = null;
[XmlIgnore]
public System.Collections.Generic.List<string> Tests
{
get { return tests; }
set { tests = value; }
}
[XmlArray("Tests")]
public string[] TestsArray
{
get
{
return (Tests == null ? null : Tests.ToArray());
}
set
{
if (value == null)
return;
(Tests = Tests ?? new List<string>(value.Length)).AddRange(value);
}
}
}
Sample .Net fiddle showing that Tests
is allocated only when appropriate.
回答2:
We wound up here after a google search for the same issue. What we ended up doing was checking for Count == 0, after deserialization, and manually setting the property to null;
...
var varMyDeserializedClass = MyXmlSerializer.Deserialize(new StringReader(myInput));
if (varMyDeserializedClass.ListProperty.Count == 0)
{
varMyDeserializedClass.ListProperty = null;
}
...
It's a cheap workaround, but provides the expected result and is useful to avoid refactoring or redesign.
来源:https://stackoverflow.com/questions/45358844/c-sharp-xml-serializer-deserializes-list-to-count-of-0-instead-of-null