- 
                Notifications
    
You must be signed in to change notification settings  - Fork 5.2k
 
Description
Description
When using an XmlSerializer in ReflectionOnly mode (Running in iOS) to serialize an object of a type where the base class contains at least one "ShouldSerialize" method it thows an InvalidOperationException.
Reproduction Steps
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleTest
{
    public class TestBase
    {
        public string BaseProp { get; set; }
        public bool ShouldSerializeBaseProp() => false;
    }
    public class Test : TestBase
    {
        public string TestProp { get; set; }
        public bool ShouldSerializeTestProp() => false;
    }
    public static class Program
    {
        public static void Main(string[] args)
        {
            var serializer = new XmlSerializer(typeof(Test));
            var obj = new Test()
            {
                TestProp = "1",
                BaseProp = "2",
            };
            var output = new StringBuilder();
            using (var xmlWriter = XmlWriter.Create(output))
            {
                serializer.Serialize(xmlWriter, obj);
            }
        }
    }
}Use the code above and force the XmlSerializer into "ReflectionOnly" mode by adding this to the csproj:
<ItemGroup>
	<RuntimeHostConfigurationOption Include="System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported" Value="false" />
</ItemGroup>Expected behavior
Output contains an "empty" <Test /> XML
Actual behavior
This exception is thrown:
System.InvalidOperationException: There was an error generating the XML document.
 ---> System.NullReferenceException: Object reference not set to an instance of an object
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteStructMethod(StructMapping mapping, String n, String ns, Object o, Boolean isNullable, Boolean needType)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElement(Object o, ElementAccessor element, Boolean writeAccessor)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElements(Object o, Object choiceSource, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Boolean writeAccessors, Boolean isNullable)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteArrayItems(ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Object o, Object choiceSources)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElement(Object o, ElementAccessor element, Boolean writeAccessor)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElements(Object o, Object choiceSource, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Boolean writeAccessors, Boolean isNullable)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteMember(Object o, Object choiceSource, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, TypeDesc memberTypeDesc, Boolean writeAccessors)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteStructMethod(StructMapping mapping, String n, String ns, Object o, Boolean isNullable, Boolean needType)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElement(Object o, ElementAccessor element, Boolean writeAccessor)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElements(Object o, Object choiceSource, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Boolean writeAccessors, Boolean isNullable)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteMember(Object o, Object choiceSource, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, TypeDesc memberTypeDesc, Boolean writeAccessors)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.GenerateTypeElement(Object o, XmlTypeMapping xmlMapping)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteObjectOfTypeElement(Object o, XmlTypeMapping mapping)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteObject(Object o)
   at System.Xml.Serialization.XmlSerializer.SerializeUsingReflection(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle)
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces)Regression?
Noticed this while porting an old Xamarin.Forms app to MAUI. It worked back then but not sure if that is relevant here.
Known Workarounds
Either migrate from ShouldSerialize methods to Specified properties:
public class TestBase
{
    public string BaseProp { get; set; }
    public bool BasePropSpecified => false;
}Or also implement the ShouldSerialize method in derived types:
public class Test : TestBase
{
    public string TestProp { get; set; }
    public bool ShouldSerializeTestProp() => false;
    // Copied from base type to prevent serializer exception
    public new bool ShouldSerializeBaseProp() => false;
}Configuration
Initially noticed this on net9.0-ios but is reproducible on other platforms.
Other information
I first tried to work around the problem by generating serializer assemblies but noticed that they are not used when dynamic code is not supported.
I understand why generation of new temporary assemblies is not supported in that scenario but why cant we load and use already generated assemblies?
Maybe adding a new mode PreGenOnlyWithReflectionAsBackup would be a good idea here?
Then after searching the XmlSerializer code I believe that the exception is thrown here:
Lines 662 to 667 in 8d3c401
| if (m.CheckShouldPersist) | |
| { | |
| string methodInvoke = $"ShouldSerialize{m.Name}"; | |
| MethodInfo method = o!.GetType().GetMethod(methodInvoke, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)!; | |
| shouldPersist = (bool)method.Invoke(o, Array.Empty<object>())!; | |
| } | 
Lines 694 to 699 in 8d3c401
| if (m.CheckShouldPersist) | |
| { | |
| string methodInvoke = $"ShouldSerialize{m.Name}"; | |
| MethodInfo method = o!.GetType().GetMethod(methodInvoke, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)!; | |
| shouldPersist = (bool)method.Invoke(o, Array.Empty<object>())!; | |
| } | 
The BindingFlags.DeclaredOnly specified here causes it to ignore everything on the base types therefor not finding the method and returning null.
When looking at the generated serializer assembly the check looks like this:
if (((TestBase)o).ShouldSerializeBaseProp())
{
    // Write Tag
}