c# - entre - .NET no puede deserializar estructuras anidadas?
estructuras de datos en c# (1)
TLDR (para los skimmers): esta publicación consta de dos partes.
Parte 1: Una introducción rápida a Protobuf. Los atributos se usan aquí.
Parte 2: la respuesta real a la pregunta: para configurar la serialización sin modificar la biblioteca heredada
Ok, voy a intentarlo.
El problema parece ser que está utilizando Compact Framework, que no tiene las mismas capacidades de serialización / deserialización que el .NET framework completo. Entonces, necesitamos una serialización personalizada aquí.
Siguiendo con la filosofía del Compact Framework, creo que también quiere algo que funcione bien y tenga una huella pequeña. Así que elegí Protobuf para la tarea (que también es aproximadamente 12 veces más rápido que XmlSerializer )
Puede instalarlo ejecutando este comando:
Install-Package protobuf-net
Comencemos con la manera fácil: agregando atributos a su modelo. La configuración sin atributos viene a continuación, ya que señaló que el modelo original no puede / no debe modificarse. Esto es solo para ilustracion.
Decorado con los atributos adecuados, su modelo se vería así:
Parte 1: Configuración con atributos.
Repito, esta parte es solo para fines ilustrativos, sigue leyendo "Configuración sin atributos"
[ProtoContract]
public struct Fred
{
[ProtoMember(1)]
public string Name;
}
[ProtoContract]
public struct Middle
{
[ProtoMember(1)]
public Fred[] Freds;
}
[ProtoContract]
public struct Top
{
[ProtoMember(1)]
public Middle Middle;
[ProtoMember(2)]
public Fred[] Freds;
}
Lo único que debe notar aquí es el uso de miembros numerados, llamados claves. Es esencialmente lo mismo que darles nombres de propiedad en el caso de la serialización JSON o XML, excepto que esta es la manera protobuf de hacerlo. Simplemente asigna un valor entero único a cada miembro dentro de la misma clase, y la mayoría de las veces lo hace allí.
Para mayor comodidad, agreguemos un generador simple desde el cual podemos instanciar un Top
que es similar al de su ejemplo:
public class TopTestBuilder
{
public Top BuildDefaultTestTop()
{
var top = new Top
{
Middle = new Middle
{
Freds = new[]
{
new Fred {Name = "Fred20"},
new Fred {Name = "Fred21"}
}
},
Freds = new[]
{
new Fred {Name = "Fred10"},
new Fred {Name = "Fred11"}
}
};
return top;
}
}
Podemos serializarlo así:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
Protobuf.Serializer.Serialize(stream, topIn);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
// Output: "/nDC4/n/b/nACKFred20/n/b/nACKFred21DC2/b/nACKFred10DC2/b/nACKFred11"
Y deserializarlo así:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = Protobuf.Serializer.Deserialize<Top>(stream);
}
Como puede ver, hay un poco de fontanería para MemoryStreams
, pero aparte de eso, debería resultarle familiar cómo funcionan otros tipos de serialización. Del mismo modo, todo se puede lograr configurando un TypeModel
personalizado, lo que permite que la serialización se desacople por completo del modelo.
Parte 2: Configuración sin atributos
De forma predeterminada, Protobuf usa los atributos para definir TypeModel
y luego lo almacena en ProtoBuf.Meta.RuntimeTypeModel.Default
. Esta propiedad se usa cuando Protobuf.Serializer
directamente al Protobuf.Serializer
estático. También podemos definir el nuestro. Me llevó un poco de tic-tac (nota para mí: RTFM) para que funcione, pero resultó ser casi tan simple:
var model = TypeModel.Create();
// The first parameter (maps to ProtoContractAttribute) is the Type to be included.
// The second parameter determines whether to apply default behavior,
// based on the attributes. Since we''re not using those, this has no effect.
model.Add(typeof(Fred), false);
model.Add(typeof(Middle), false);
model.Add(typeof(Top), false);
// The newly added MetaTypes can be accessed through their respective Type indices.
// The first parameter is the unique member number, similar to ProtoMemberAttribute.
// The second parameter is the name of the member as it is declared in the class.
// When the member is a list:
// The third parameter is the Type for the items.
// The fourth parameter is the Type for the list itself.
model[typeof(Fred)].Add(1, "Name");
model[typeof(Middle)].Add(1, "Freds", typeof(Fred), typeof(Fred[]));
model[typeof(Top)].Add(1, "Middle");
model[typeof(Top)].Add(2, "Freds", typeof(Fred), typeof(Fred[]));
Ahora todo lo que tenemos que hacer es cambiar una línea de código para ambas funciones:
Publicar por fascículos:
Top topIn = new TopTestBuilder().BuildDefaultTestTop();
string serialized;
using (var stream = new MemoryStream())
{
model.Serialize(stream, top);
stream.Position = 0;
var reader = new StreamReader(stream);
serialized = reader.ReadToEnd();
}
Deserializar:
Top topOut;
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
writer.Write(serialized);
writer.Flush();
stream.Position = 0;
topOut = (Top) _model.Deserialize(stream, null, typeof (Top));
}
Y funciona igual. Tal vez, agregue una clase para mantener las cosas organizadas; proporciónele dos métodos públicos Serialize
y Deserialize
, y un método privado BuildTypeModel
(para llamar desde el constructor y almacenarlo en un campo en el serializador).
Su código de llamada terminaría luciendo algo como esto:
var serializer = new CustomProtoBufSerializer();
var serialized = serializer.Serialize(someClassInput);
SomeClass someClassOutput = serializer.Deserialize(serialized);
Sin embargo, una cosa quedó clara rápidamente: Protobuf no ha sido documentado y probado tan exhaustivamente como la mayoría de los serializadores JSON y XML. Esto, junto con los resultados de serialización que no son legibles para los humanos, podría ser un inconveniente en algunas situaciones. Aparte de eso, parece que es rápido, liviano y compatible con muchos entornos diferentes.
La ausencia de resolución de tipo automática me molestó un poco, así que busqué y encontré algo que parece bastante interesante: Protobuf T4 TypeModel Generator . No he podido probarlo todavía. Si las personas están interesadas, podría hacerlo más tarde y actualizar la respuesta con una solución más genérica.
Avíseme si tiene algún problema para que funcione.
Me estoy encontrando con problemas para obtener C # (VS2008, Compact Framework, .NET es la versión 3.5 SP1) para deserializar con éxito las estructuras anidadas. El problema solo aparece en CF cuando estoy ejecutando el emulador para el dispositivo móvil (estoy usando el emulador "Pocket PC 2003 Second Edition"), el mismo código exacto que se ejecuta en mi cuadro de Windows no tiene el mismo problema.
Aquí está mi código:
public struct Fred
{
public string Name;
}
public struct Middle
{
public Fred[] Freds;
}
public struct Top
{
public Middle Middle;
public Fred[] Freds;
}
public static void Test()
{
Top top = new Top();
top.Middle.Freds = new Fred[2];
top.Middle.Freds[0].Name = "Fred20";
top.Middle.Freds[1].Name = "Fred21";
top.Freds = new Fred[2];
top.Freds[0].Name = "Fred10";
top.Freds[1].Name = "Fred11";
StringBuilder sb = new StringBuilder();
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(top.GetType());
using (StringWriter sw = new StringWriter(sb))
{
x.Serialize(sw, top);
}
string xml = sb.ToString();
string[] lines = xml.Split(new char[] { ''/r'', ''/n'' });
foreach (string line in lines)
{
Debug.WriteLine(" " + line.Trim());
}
MemoryStream ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml));
StreamReader sr = new StreamReader(ms);
object o = x.Deserialize(sr);
Debug.WriteLine("Deserialized into " + o);
Top go2 = (Top)o;
if (go2.Freds == null)
Debug.WriteLine(" go2.Freds is null");
else
Debug.WriteLine(" go2.Freds[0].Name is /"" + go2.Freds[0].Name + "/"");
if (go2.Middle.Freds == null)
Debug.WriteLine(" go2.Middle.Freds is null");
else
Debug.WriteLine(" go2.Middle.Freds[0].Name is /"" + go2.Middle.Freds[0].Name + "/"");
}
Cuando ejecuto esto, el XML que crea se ve bien:
<?xml version="1.0" encoding="utf-16"?>
<Top xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Middle>
<Freds>
<Fred>
<Name>Fred20</Name>
</Fred>
<Fred>
<Name>Fred21</Name>
</Fred>
</Freds>
</Middle>
<Freds>
<Fred>
<Name>Fred10</Name>
</Fred>
<Fred>
<Name>Fred11</Name>
</Fred>
</Freds>
</Top>
pero C # no puede deserializar con éxito este XML; la salida de la consola es esta:
Deserialized into Top
go2.Freds[0].Name is "Fred10"
go2.Middle.Freds is null
xsd tiene problemas similares:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="Top" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="Top" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Middle">
<xs:complexType>
<xs:sequence>
<xs:element name="Freds" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Fred" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
¿Acabo de encontrar un error C #? ¿O me estoy perdiendo algo obvio?
Nota: No es un problema usar el nombre dos veces, si creo una estructura llamada George que es idéntica a Fred, y cambio el contenido de Middle a public George [] George, el problema no es mejor.