Saltar al contenido

¿Por qué XmlSerializer no serializa el valor de enumeración en .Net Core pero funciona bien en .NET Framework?

Solución:

Este cambio radical se debe a una diferencia en las implementaciones en XmlSerializationWriter.WriteTypedPrimitive(string name, string ns, object o, bool xsiType) entre .NET Core y .NET Framework.

Esto se puede ver en los siguientes dos violines de demostración:

  1. .NET Core 3.1.0, que genera una excepción de la siguiente manera:

    System.InvalidOperationException: There was an error generating the XML document.
    ---> System.InvalidOperationException: The type MyEnum may not be used in this context.
    at System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(String name, String ns, Object o, Boolean xsiType)
    
  2. .NET Framework 4.7.3460.0, que serializa un new ValueContainer { Value = MyEnum.One } como sigue:

    <ValueContainer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Value xsi:type="xsd:int">0</Value>
    </ValueContainer>
    

    Tenga en cuenta que, mientras se genera XML, la información sobre el enum tipo presente en Value no está incluido y, en cambio, solo el tipo subyacente int se muestra en el xsi:type atributo.

Entonces, ¿de dónde surge la diferencia? La fuente de referencia del marco completo se puede ver aquí y comienza:

    protected void WriteTypedPrimitive(string name, string ns, object o, bool xsiType) {
        string value = null;
        string type;
        string typeNs = XmlSchema.Namespace;
        bool writeRaw = true;
        bool writeDirect = false;
        Type t = o.GetType();
        bool wroteStartElement = false;

        switch (Type.GetTypeCode
        case TypeCode.String:
            value = (string)o;
            type = "string";
            writeRaw = false;
            break;
        case TypeCode.Int32:
            value = XmlConvert.ToString((int)o);
            type = "int";
            break;

Dado que el entrante object o es en realidad una caja Enum.One, luego Type.GetTypeCode(Type type) devuelve un TypeCode apropiado para el tipo subyacente de la enumeración, aquí TypeCode.Int32, serializando así su valor con éxito.

La fuente de referencia principal de .Net actual está aquí y parece superficialmente similar:

    protected void WriteTypedPrimitive(string name, string ns, object o, bool xsiType)
    {
        string value = null;
        string type;
        string typeNs = XmlSchema.Namespace;
        bool writeRaw = true;
        bool writeDirect = false;
        Type t = o.GetType();
        bool wroteStartElement = false;

        switch (t.GetTypeCode())
        {
            case TypeCode.String:
                value = (string)o;
                type = "string";
                writeRaw = false;
                break;
            case TypeCode.Int32:
                value = XmlConvert.ToString((int)o);
                type = "int";
                break;

Pero espera, ¿cuál es este método? t.GetTypeCode()? No hay un método de instancia GetTypeCode() sobre Type por lo que debe ser algún tipo de método de extensión. ¿Pero donde? Una búsqueda rápida de la fuente de referencia arrojó al menos tres diferentes, inconsistentes public static TypeCode GetTypeCode(this Type type) métodos:

  1. System.Runtime.Serialization.TypeExtensionMethods.GetTypeCode(this Type type).

  2. System.Dynamic.Utils.TypeExtensions.GetTypeCode(this Type type).

  3. System.Xml.Serialization.TypeExtensionMethods.GetTypeCode(this Type type).

    Ya que System.Xml.Serialization es el espacio de nombres de XmlSerializationWriter Creo que este es el que se usa. Y no llama Type.GetTypeCode():

    public static TypeCode GetTypeCode(this Type type)
    {
        if (type == null)
        {
            return TypeCode.Empty;
        }
        else if (type == typeof(bool))
        {
            return TypeCode.Boolean;
        }
        else if (type == typeof(char))
        {
            return TypeCode.Char;
        }
        else if (type == typeof(sbyte))
        {
            return TypeCode.SByte;
        }
        else if (type == typeof(byte))
        {
            return TypeCode.Byte;
        }
        else if (type == typeof(short))
        {
            return TypeCode.Int16;
        }
        else if (type == typeof(ushort))
        {
            return TypeCode.UInt16;
        }
        else if (type == typeof(int))
        {
            return TypeCode.Int32;
        }
        else if (type == typeof(uint))
        {
            return TypeCode.UInt32;
        }
        else if (type == typeof(long))
        {
            return TypeCode.Int64;
        }
        else if (type == typeof(ulong))
        {
            return TypeCode.UInt64;
        }
        else if (type == typeof(float))
        {
            return TypeCode.Single;
        }
        else if (type == typeof(double))
        {
            return TypeCode.Double;
        }
        else if (type == typeof(decimal))
        {
            return TypeCode.Decimal;
        }
        else if (type == typeof(DateTime))
        {
            return TypeCode.DateTime;
        }
        else if (type == typeof(string))
        {
            return TypeCode.String;
        }
        else
        {
            return TypeCode.Object;
        }
    }
    

    Así, cuando pasó un enum escribe, TypeCode.Object Será devuelto.

El reemplazo de System.Type.GetTypeCode(Type t) con System.Xml.Serialization.TypeExtensionMethods.GetTypeCode(this Type type) es el cambio radical que está causando el error de serialización.

Todo esto plantea la pregunta, ¿Es este cambio radical un error o una corrección de error?

XmlSerializer está diseñado para el intercambio de objetos serializables: generalmente se niega a serializar cualquier tipo que no pueda deserializar sin pérdida de datos. Pero en su caso, los datos se están perdiendo, ya que enum los valores se degradan en valores enteros. Entonces, este cambio de comportamiento puede ser intencional. Sin embargo, podría abrir un problema aquí preguntando si el cambio radical fue intencional.

Para evitar la excepción, debe declarar correctamente todo lo esperado enum tipos (y otros tipos) con [XmlInclude(typeof(TEnum))] atributos en ValueContainer:

[XmlInclude(typeof(MyEnum)), XmlInclude(typeof(SomeOtherEnum)), XmlInclude(typeof(SomeOtherClass)) /* Include all other expected custom types here*/]
public class ValueContainer
{
    public object Value;
}

Esta es la forma prevista de serializar miembros polimórficos utilizando XmlSerializery asegura que la información de tipo sea de ida y vuelta. Funciona tanto en .NET Core como en .NET Full Framework. Para preguntas relacionadas, consulte Serializar una clase con una enumeración genérica que puede ser de diferentes tipos de enumeración y Usando XmlSerializer para serializar clases derivadas.

Demostración del violín n. ° 3 aquí.

Las soluciones alternativas sugeridas en esta respuesta por Eldar también evitan la excepción, pero convirtiendo la enum a una int causará pérdida de información de tipo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *