Serializar um int anulável

92

Eu tenho uma classe com um int anulável? tipo de dados definido para serializar como um elemento xml. Existe alguma maneira de configurá-lo para que o serializador xml não serialize o elemento se o valor for nulo?

Tentei adicionar o atributo [System.Xml.Serialization.XmlElement (IsNullable = false)], mas recebo uma exceção de serialização de tempo de execução informando que houve um erro refletindo o tipo, porque "IsNullable não pode ser definido como 'false 'para um tipo Nullable. Considere usar o tipo' System.Int32 'ou remover a propriedade IsNullable do atributo XmlElement. "

[Serializable]
[System.Xml.Serialization.XmlRoot("Score", Namespace = "http://mycomp.com/test/score/v1")]
public class Score
{
    private int? iID_m;
    ...

    /// <summary>
    /// 
    /// </summary>        
    public int? ID 
    { 
        get 
        { 
            return iID_m; 
        } 
        set 
        { 
            iID_m = value; 
        } 
    }
     ...
}

A classe acima será serializada para:

<Score xmlns="http://mycomp.com/test/score/v1">
    <ID xsi:nil="true" />
</Score>

Mas para IDs que são nulos, eu não quero o elemento ID, principalmente porque quando eu uso OPENXML no MSSQL, ele retorna 0 em vez de nulo para um elemento que se parece com

Jeremy
fonte

Respostas:

149

XmlSerializer suporta o ShouldSerialize{Foo}()padrão, então você pode adicionar um método:

public bool ShouldSerializeID() {return ID.HasValue;}

Há também o {Foo}Specifiedpadrão - não tenho certeza se o XmlSerializer oferece suporte a ele.

Marc Gravell
fonte
8
XmlSerializer também oferece suporte ao padrão [Foo} especificado.
David Schmitt,
23
A página relevante está aqui: msdn.microsoft.com/en-us/library/53b8022e%28VS.71%29.aspx
cbp
1
Alguma maneira de usar ShouldSerialize <prop> com propriedades geradas automaticamente? ou seja, nenhuma variável local.
Jay
1
@Jay: Não há necessidade de um. Você pode simplesmente ligar HasValuepara a propriedade.
Steven Sudit de
1
@marque se, para membro (propriedade / campo) Foovocê também tiver um public bool FooSpecified {get {...} set {...}}, o getserá usado para ver se Foodeve ser serializado e o setserá chamado ao atribuir um valor a Foodurante a desserialização.
Marc Gravell
26

Estou usando este micropadrão para implementar a serialização anulável:

[XmlIgnore]
public double? SomeValue { get; set; }

[XmlAttribute("SomeValue")] // or [XmlElement("SomeValue")]
[EditorBrowsable(EditorBrowsableState.Never)]
public double XmlSomeValue { get { return SomeValue.Value; } set { SomeValue= value; } }  
[EditorBrowsable(EditorBrowsableState.Never)]
public bool XmlSomeValueSpecified { get { return SomeValue.HasValue; } }

Isso fornece a interface certa para o usuário sem concessões e ainda faz a coisa certa ao serializar.

David Schmitt
fonte
1
Como SomeValue pode ser nulo ... public double XmlAlgumaValor {get {return SomeValue.HasValue? SomeValue.Value: 0; } defina {SomeValue = value; }}
Doug Domeny
XmlSomeValue só deve ser usado pelo XmlSerializer, que só o tocará quando XmlSomeValueSpecified for verdadeiro (ou seja, SomeValue.Value não é nulo.
David Schmitt
@pettys: É XML, o que você esperava? ;-)
David Schmitt
A resposta aceita é de 2008. Esta deve ser a que está agora. Resposta interessante relacionada a Specified vs ShouldSerialize
daniloquio
Definitivamente deve ser a melhor resposta.
tyteen4a03
12

Eu descobri uma solução alternativa utilizando duas propriedades. Um int? propriedade com um atributo XmlIgnore e uma propriedade de objeto que é serializada.

    /// <summary>
    /// Score db record
    /// </summary>        
    [System.Xml.Serialization.XmlIgnore()]
    public int? ID 
    { 
        get 
        { 
            return iID_m; 
        } 
        set 
        { 
            iID_m = value; 
        } 
    }

    /// <summary>
    /// Score db record
    /// </summary>        
    [System.Xml.Serialization.XmlElement("ID",IsNullable = false)]
    public object IDValue
    {
        get
        {
            return ID;
        }
        set
        {
            if (value == null)
            {
                ID = null;
            }
            else if (value is int || value is int?)
            {
                ID = (int)value;
            }
            else
            {
                ID = int.Parse(value.ToString());
            }
        }
    }
Jeremy
fonte
Esta solução é ótima, pois também permite que NULL seja codificado como valor de "espaço reservado" para clientes, que não reconhecem NULLs em ints, ou seja, Flex.
Kuba Wyrostek
Você pode adicionar [EditorBrowsable (EditorBrowsableState.Never)] à propriedade xml serializada para aviá-la durante a codificação
Antonio Rodríguez
6

Nossa, obrigado, essa pergunta / resposta realmente me ajudou. Eu amo Stackoverflow.

Tornei o que você está fazendo acima um pouco mais genérico. Tudo o que realmente estamos procurando é ter Nullable com comportamento de serialização ligeiramente diferente. Usei o Reflector para construir meu próprio Nullable e adicionei algumas coisas aqui e ali para fazer a serialização XML funcionar da maneira que desejamos. Parece funcionar muito bem:

public class Nullable<T>
{
    public Nullable(T value)
    {
        _value = value;
        _hasValue = true;
    }

    public Nullable()
    {
        _hasValue = false;
    }

    [XmlText]
    public T Value
    {
        get
        {
            if (!HasValue)
                throw new InvalidOperationException();
            return _value;
        }
        set
        {
            _value = value;
            _hasValue = true;
        }
    }

    [XmlIgnore]
    public bool HasValue
        { get { return _hasValue; } }

    public T GetValueOrDefault()
        { return _value; }
    public T GetValueOrDefault(T i_defaultValue)
        { return HasValue ? _value : i_defaultValue; }

    public static explicit operator T(Nullable<T> i_value)
        { return i_value.Value; }
    public static implicit operator Nullable<T>(T i_value)
        { return new Nullable<T>(i_value); }

    public override bool Equals(object i_other)
    {
        if (!HasValue)
            return (i_other == null);
        if (i_other == null)
            return false;
        return _value.Equals(i_other);
    }

    public override int GetHashCode()
    {
        if (!HasValue)
            return 0;
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        if (!HasValue)
            return "";
        return _value.ToString();
    }

    bool _hasValue;
    T    _value;
}

Você perde a capacidade de ter seus membros como int? e assim por diante (tem que usar Nullable <int> ao invés), mas exceto que todo o comportamento permanece o mesmo.

Scobi
fonte
1
Isto lança um System.ExecutionEngineExceptionem XmlSerializer.Serializemim.
Martin Braun
1

Infelizmente, os comportamentos que você descreve são documentados com precisão como tal nos documentos de XmlElementAttribute.IsNullable.

Serge Wautier
fonte
1

Postagem muito útil ajudou muito.

Optei por ir com a revisão de Scott para o tipo de dados Nullable (Of T), no entanto, o código postado ainda serializa o elemento Nullable quando ele é Null - embora sem o atributo "xs: nil = 'true'".

Eu precisava forçar o serializador a descartar a tag completamente, então simplesmente implementei IXmlSerializable na estrutura (isso está em VB, mas você entendeu):

  '----------------------------------------------------------------------------
  ' GetSchema
  '----------------------------------------------------------------------------
  Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
    Return Nothing
  End Function

  '----------------------------------------------------------------------------
  ' ReadXml
  '----------------------------------------------------------------------------
  Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
    If (Not reader.IsEmptyElement) Then
      If (reader.Read AndAlso reader.NodeType = System.Xml.XmlNodeType.Text) Then
         Me._value = reader.ReadContentAs(GetType(T), Nothing)
      End If
    End If
  End Sub

  '----------------------------------------------------------------------------
  ' WriteXml
  '----------------------------------------------------------------------------
  Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
    If (_hasValue) Then
      writer.WriteValue(Me.Value)
    End If
  End Sub

Prefiro esse método a usar o padrão (foo) Especificado, pois isso requer a adição de cargas de balde de propriedades redundantes aos meus objetos, enquanto o uso do novo tipo Nullable requer apenas a redigitação das propriedades.

James Close
fonte