Pistas de serialização XML do .NET? [fechadas]

121

Encontrei algumas dicas ao fazer serialização XML em C # que pensei em compartilhar:


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{      
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();

        if (wasEmpty)
            return;

        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");

            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}

Alguma outra dica de serialização XML por aí?

kurious
fonte
Lookin para mais gotchas lol, você pode ser capaz de me ajudar: stackoverflow.com/questions/2663836/...
Shimmy Weitzhandler
1
Além disso, você vai querer dar uma olhada na implementação do dicionário serialzable de Charles Feduke, ele fez o escritor xml perceber entre membros atribuíveis a membros regulares a serem serializados pelo serializador padrão: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler
Isso não parece capturar todas as dicas. Estou definindo o IEqualityComparer no construtor, mas isso não é serializado neste código. Alguma idéia de como estender este dicionário para incluir esta informação? essas informações poderiam ser tratadas através do objeto Type?
ColinCren

Respostas:

27

Outra grande dificuldade: ao produzir XML através de uma página da Web (ASP.NET), você não deseja incluir a Marca de Pedido de Byte Unicode . Obviamente, as maneiras de usar ou não a BOM são quase as mesmas:

RUIM (inclui lista técnica):

XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8);

BOA:

XmlTextWriter  wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false))

Você pode explicitamente passar false para indicar que não deseja a BOM. Observe a clara e óbvia diferença entre Encoding.UTF8e UTF8Encoding.

Os três bytes extras de BOM no início são (0xEFBBBF) ou (239 187 191).

Referência: http://chrislaco.com/blog/trou Troubleshooting - common - problems - with - the - xmlserializer/

Kalid
fonte
4
Seu comentário seria ainda mais útil se você dissesse não apenas o quê, mas por quê.
Neil
1
Esta não é realmente relacionados com a serialização XML ... é só uma questão XmlTextWriter
Thomas Levesque
7
-1: não está relacionado à pergunta e você não deve usá-lo XmlTextWriterno .NET 2.0 ou superior.
318 John Saunders
Link de referência muito útil. Obrigado.
Anil Vangari
21

Ainda não posso fazer comentários, então comentarei o post do Dr8k e farei outra observação. Variáveis ​​privadas que são expostas como propriedades públicas de getter / setter e são serializadas / desserializadas como tais por meio dessas propriedades. Fizemos isso no meu antigo emprego o tempo todo.

Porém, uma coisa a ser observada é que, se você tiver alguma lógica nessas propriedades, a lógica será executada; portanto, às vezes, a ordem da serialização é realmente importante. Os membros são implicitamente ordenados pela maneira como são ordenados no código, mas não há garantias, especialmente quando você está herdando outro objeto. Ordená-los explicitamente é uma dor na parte traseira.

Eu fui queimado por isso no passado.

Charles Graham
fonte
17
Encontrei este post enquanto procurava maneiras de definir explicitamente a ordem dos campos. Isso é feito com os atributos: [XmlElementAttribute (Order = 1)] public int Field {...} Desvantagem: o atributo deve ser especificado para TODOS os campos da classe e todos os seus descendentes! IMO Você deve adicionar isso à sua postagem.
Cristian Diaconescu
15

Ao serializar em uma sequência XML a partir de um fluxo de memória, use MemoryStream # ToArray () em vez de MemoryStream # GetBuffer () ou você terminará com caracteres indesejados que não serão desserializados adequadamente (devido ao buffer extra alocado).

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx

realgt
fonte
3
diretamente dos documentos "Observe que o buffer contém bytes alocados que podem não ser utilizados. Por exemplo, se a cadeia" test "for gravada no objeto MemoryStream, o comprimento do buffer retornado de GetBuffer é 256, não 4, com 252 bytes Para obter apenas os dados no buffer, use o método ToArray; no entanto, o ToArray cria uma cópia dos dados na memória. " msdn.microsoft.com/en-us/library/…
realgt 21/09/09
só agora vi isso. Já não parece bobagem.
John Saunders
Nunca ouvi isso antes, o que é útil na depuração.
Ricky
10

Se o serializador encontrar um membro / propriedade que tenha uma interface como seu tipo, ele não será serializado. Por exemplo, o seguinte não será serializado para XML:

public class ValuePair
{
    public ICompareable Value1 { get; set; }
    public ICompareable Value2 { get; set; }
}

Embora isso seja serializado:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
fonte
Se você receber uma exceção com a mensagem "Tipo não resolvido para membro ...", isso pode estar acontecendo.
Kyle Krull #
9

IEnumerables<T>que são gerados por meio de retornos de rendimento não são serializáveis. Isso ocorre porque o compilador gera uma classe separada para implementar retorno de rendimento e essa classe não é marcada como serializável.

abatishchev
fonte
Isso se aplica à serialização 'other', ou seja, ao atributo [Serializable]. Isso também não funciona para o XmlSerializer.
Tim Robinson
8

Você não pode serializar propriedades somente leitura. Você deve ter um getter e um setter, mesmo que nunca pretenda usar a desserialização para transformar XML em um objeto.

Pelo mesmo motivo, você não pode serializar propriedades que retornam interfaces: o desserializador não saberia qual classe concreta instanciar.

Tim Robinson
fonte
1
Na verdade, você pode serializar uma propriedade de coleção, mesmo se ele não tem setter, mas tem que ser inicializado no construtor para que a desserialização pode adicionar itens a ela
Thomas Levesque
7

Ah, eis uma boa: como o código de serialização XML é gerado e colocado em uma DLL separada, você não recebe nenhum erro significativo quando há um erro no código que interrompe o serializador. Apenas algo como "incapaz de localizar s3d3fsdf.dll". Agradável.

Eric Z Beard
fonte
11
Você pode gerar essa DLL antecipadamente usando o XML "Serializer Generator Tool (Sgen.exe)" e implantar com seu aplicativo.
huseyint
6

Não é possível serializar um objeto que não possui um construtor sem parâmetros (apenas foi mordido por esse).

E, por algum motivo, nas seguintes propriedades, o Value é serializado, mas não o FullName:

    public string FullName { get; set; }
    public double Value { get; set; }

Nunca resolvi o porquê, apenas mudei Valor para interno ...

Benjol
fonte
4
O construtor sem parâmetros pode ser privado / protegido. Isso será suficiente para o serializador XML. O problema com FullName é realmente estranho, não deveria acontecer ...
Max Galkin
@ Yacoder: Talvez porque não, double?mas apenas double?
abatishchev
FullName foi, provavelmente, nulle não, portanto, gerar qualquer XML quando serializado
Jesper
4

Se o assembly gerado por serialização XML não estiver no mesmo contexto de carregamento que o código que está tentando usá-lo, você encontrará erros impressionantes, como:

System.InvalidOperationException: There was an error generating the XML document.
---System.InvalidCastException: Unable to cast object
of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at
Microsoft.Xml.Serialization.GeneratedAssembly.
  XmlSerializationWriterSettings.Write3_Settings(Object o)

A causa disso para mim foi um plugin carregado usando o contexto LoadFrom, que tem muitas desvantagens em usar o contexto Load. Um pouco divertido rastrear esse abaixo.

user7116
fonte
4

Se você tentar serializar uma matriz List<T>ou IEnumerable<T>que contenha instâncias de subclasses de T, precisará usar o XmlArrayItemAttribute para listar todos os subtipos que estão sendo usados. Caso contrário, você receberá uma mensagem inútilSystem.InvalidOperationException em tempo de execução ao serializar.

Aqui está parte de um exemplo completo da documentação

public class Group
{  
   /* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base 
      type (Employee) and derived type (Manager) into serialized arrays. */

   [XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))]
   public Employee[] Employees;
MarkJ
fonte
3

Variáveis ​​/ propriedades privadas não são serializadas no mecanismo padrão para serialização XML, mas estão na serialização binária.

Charles Graham
fonte
2
Sim, se você estiver usando a serialização XML "padrão". Você pode especificar uma lógica de serialização XML personalizada implementando IXmlSerializable em sua classe e serializar todos os campos particulares que você precisa / deseja.
Max Galkin
1
Bem, isso é verdade. Eu vou editar isso. Mas implementar essa interface é meio chato do que eu me lembro.
Charles Graham
3

As propriedades marcadas com o Obsoleteatributo não são serializadas. Eu não testei com Deprecatedatributo, mas presumo que funcionaria da mesma maneira.

James Hulse
fonte
2

Eu realmente não posso explicar este, mas achei que isso não seria serializado:

[XmlElement("item")]
public myClass[] item
{
    get { return this.privateList.ToArray(); }
}

mas isso irá:

[XmlElement("item")]
public List<myClass> item
{
    get { return this.privateList; }
}

E também é importante notar que, se você estiver serializando para um fluxo de memórias, convém procurar 0 antes de usá-lo.

annakata
fonte
Eu acho que é porque não pode reconstruí-lo. No segundo exemplo, ele pode chamar item.Add () para adicionar itens à Lista. Não pode fazê-lo no primeiro.
ilitirit 26/09/08
18
Use: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]]
RvdK
1
um brinde por isso! aprender alguma coisa todos os dias
annakata
2

Tenha cuidado com os tipos de serialização sem serialização explícita, pois isso pode resultar em atrasos, enquanto o .Net os cria. Descobri isso recentemente ao serializar RSAParameters .

Keith
fonte
2

Se o seu XSD faz uso de grupos de substituição, é provável que você não possa (des) serializá-lo automaticamente. Você precisará escrever seus próprios serializadores para lidar com esse cenário.

Por exemplo.

<xs:complexType name="MessageType" abstract="true">
    <xs:attributeGroup ref="commonMessageAttributes"/>
</xs:complexType>

<xs:element name="Message" type="MessageType"/>

<xs:element name="Envelope">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
            <xs:element ref="Message" minOccurs="0" maxOccurs="unbounded"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageA" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageB" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

Neste exemplo, um envelope pode conter mensagens. No entanto, o serializador padrão do .NET não distingue entre Message, ExampleMessageA e ExampleMessageB. Serializará somente para e da classe Message base.

ilitirit
fonte
0

Variáveis ​​/ propriedades privadas não são serializadas na serialização XML, mas estão na serialização binária.

Acredito que isso também o faça se você estiver expondo os membros privados por meio de propriedades públicas - os membros privados não são serializados, portanto todos os membros públicos estão referenciando valores nulos.

Dr8k
fonte
Isso não é verdade. O levantador da propriedade pública seria chamado e, presumivelmente, definiria o membro privado.
John Saunders