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:
-
.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)
-
.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 enValue
no está incluido y, en cambio, solo el tipo subyacenteint
se muestra en elxsi: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:
-
System.Runtime.Serialization.TypeExtensionMethods.GetTypeCode(this Type type)
. -
System.Dynamic.Utils.TypeExtensions.GetTypeCode(this Type type)
. -
System.Xml.Serialization.TypeExtensionMethods.GetTypeCode(this Type type)
.Ya que
System.Xml.Serialization
es el espacio de nombres deXmlSerializationWriter
Creo que este es el que se usa. Y no llamaType.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 XmlSerializer
y 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.