You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
IXmlSerializable makes XmlSerializer Deserialize creates instance of base class instead of derived class #101654
When a base class implements IXmlSerializable then XmlSerializer Deserializing tries to create an instance of base class and calls it's virtual ReadXml instead of the overriden ReadXml in the derived class.
Reproduction Steps
In the following example the deserializer is trying to make an instance of the base class instead of the derived one.
publicclassTestClass{[XmlElement("AClass",typeof(AClass))]List<BaseClass>objects=newList<BaseClass>();publicTestClass(){}publicvoidSaveToFile(){objects.Add(newAClass("someString"));objects.Add(newAClass("anotherString"));varxmlSerializer=newXmlSerializer(typeof(List<BaseClass>));using(varwriter=newStreamWriter("SaveList.xml")){xmlSerializer.Serialize(writer,objects);}}publicvoidLoadFromFile(){if(File.Exists("SaveList.xml")){using(varreader=newStreamReader("SaveList.xml")){vardeserializedList=newXmlSerializer(typeof(List<BaseClass>)).Deserialize(reader)asList<BaseClass>;if(deserializedList!=null&&deserializedList.Count>0){objects=deserializedList;}}}}}[XmlInclude(typeof(AClass))]publicclassBaseClass:IXmlSerializable{publicBaseClass(){}publicXmlSchemaGetSchema(){returnnull;}publicvirtualvoidReadXml(XmlReaderreader){Console.Write("It shouldn't be triggered.");}publicvirtualvoidWriteXml(XmlWriterwriter){}}publicclassAClass:BaseClass{privatestring_stringVar;publicstringStringVar{get=>_stringVar;privateset=>_stringVar=value;}publicAClass(){}publicAClass(stringstringVar){_stringVar=stringVar;}publicoverridevoidReadXml(XmlReaderreader){reader.MoveToContent();varanyElements=!reader.IsEmptyElement;reader.ReadStartElement();if(anyElements){_stringVar=reader.ReadElementContentAsString("StringVar","");reader.ReadEndElement();}}publicoverridevoidWriteXml(XmlWriterwriter){writer.WriteAttributeString("xsi","type",null,"AClass");writer.WriteElementString("StringVar",_stringVar);}}
The [XmlElement("AClass", typeof(AClass))] attribute on the TestClass.objects field is ignored by XmlSerializer because it is not serializing or deserializing a TestClass instance.
If BaseClass did not implement IXmlSerializable, then XmlSerializer.Deserialize would recognize xsi:type="AClass" and create an instance of AClass. But now when BaseClass implements IXmlSerializable, XmlSerializer ignores the xsi:type attribute.
Analysis
The behaviour seems to be the same in .NET 8.0 and in .NET Framework 4.8.
XmlSerializer in .NET Framework still includes the legacy C# code generator. That likewise has the same behaviour. The generated C# code can be debugged with this app.config:
The deserializer code generators have branches specifically for checking the xsi:type attribute when deserializing a type that implements IXmlSerializable:
Thank you for the answer. If I understand correctly, if I want to add another derived class then I need to modify the scheme.
I made something like this:
The documentation could be improved for sure. The IXmlSerializable documentation explains how to provide a schema but does not mention that XmlSerializer requires a schema for deserialising instances of a derived class. I think a note about this would be useful in the XmlIncludeAttribute documentation as well.
It makes some sense that, by implementing IXmlSerializable, the developer also takes responsibility of choosing the XmlQualifiedName for the xsi:type attribute. But perhaps XmlSerializer could be changed to require only this XmlQualifiedName and not an entire XmlSchema.
You are right. An improved documentation would be also really helpful. But personally I would prefer to keep modifications of the base class to a minimum.
Currently I've encountered another problem. I changed the BaseClass into an abstract class and updated the XmlSchemaComplexType of "BaseClass" by setting IsAbstract flag to true. Unfortunately I get an error because deserializer tries to create an instance of BaseClass instead of the derived class.
Activity
KalleOlaviNiemitalo commentedon Apr 30, 2024
The
[XmlElement("AClass", typeof(AClass))]attribute on the TestClass.objects field is ignored by XmlSerializer because it is not serializing or deserializing a TestClass instance.The generated SaveList.xml is:
If BaseClass did not implement IXmlSerializable, then XmlSerializer.Deserialize would recognize
xsi:type="AClass"and create an instance of AClass. But now when BaseClass implements IXmlSerializable, XmlSerializer ignores thexsi:typeattribute.Analysis
The behaviour seems to be the same in .NET 8.0 and in .NET Framework 4.8.
XmlSerializer in .NET Framework still includes the legacy C# code generator. That likewise has the same behaviour. The generated C# code can be debugged with this app.config:
The deserializer code generators have branches specifically for checking the
xsi:typeattribute when deserializing a type that implements IXmlSerializable:Those do not trigger with your sample code; SerializableMapping.DerivedMappings is apparently null.
Workaround (or intended use?)
If you apply XmlSchemaProviderAttribute, then the deserializer checks the
xsi:typeattribute and creates an instance of AClass:I don't know how much detail can be omitted from the XML schema.
MMariusch commentedon Apr 30, 2024
Thank you for the answer. If I understand correctly, if I want to add another derived class then I need to modify the scheme.
I made something like this:
Unfortunately this doesn't work. The deserializer skips BClass.
KalleOlaviNiemitalo commentedon May 1, 2024
Please show how you define BClass and what goes into the XML file.
MMariusch commentedon May 1, 2024
Oops. I forgot to add the XmlInclude attribute. Everything works fine. Thank you.
Getting IXmlSerializable to work takes a lot of work. That's why I'm wondering whether this shouldn't be treated as an bug.
KalleOlaviNiemitalo commentedon May 1, 2024
The documentation could be improved for sure. The IXmlSerializable documentation explains how to provide a schema but does not mention that XmlSerializer requires a schema for deserialising instances of a derived class. I think a note about this would be useful in the XmlIncludeAttribute documentation as well.
It makes some sense that, by implementing IXmlSerializable, the developer also takes responsibility of choosing the XmlQualifiedName for the
xsi:typeattribute. But perhaps XmlSerializer could be changed to require only this XmlQualifiedName and not an entire XmlSchema.MMariusch commentedon May 1, 2024
You are right. An improved documentation would be also really helpful. But personally I would prefer to keep modifications of the base class to a minimum.
Currently I've encountered another problem. I changed the BaseClass into an abstract class and updated the XmlSchemaComplexType of "BaseClass" by setting IsAbstract flag to true. Unfortunately I get an error because deserializer tries to create an instance of BaseClass instead of the derived class.
I updated my project on repo: https://github.com/MMariusch/Example
Jrpalazzo commentedon Jul 17, 2024
@MMariusch Just to contribute for future readers. If you remove the constructor from abstract BaseClass it deserializes correctly.
MMariusch commentedon Jul 20, 2024
Thank you for your suggestion. It does work, but I think a better solution would be to make the constructor protected rather than removing it.
Also, this is still a workaround because adding "IsAbstract = true" to "XmlSchemaComplexType" should be enough (if I understand correctly).