views:

227

answers:

3

Here is my class:

public class Command
{
   [XmlArray(IsNullable = true)]
   public List<Parameter> To { get; set; }
}

When I serialize an object of this class:

var s = new XmlSerializer(typeof(Command));
s.Serialize(Console.Out, new Command());

it prints as expected (xml header and default MS namespaces are omitted):

<Command><To xsi:nil="true" /></Command>

When I took this xml and tried to deserialize it I got stucked, because it always print "Not null":

var t = s.Deserialize(...);
if (t.To == null)
    Console.WriteLine("Null");
else
    Console.WriteLine("Not null");

How to force deserializer to make my list null, if it is null in xml?

A: 

I agree with @Oliver's comment, but you can solve it like this if you absolutely need it to return null. Instead of using an automatic property, create your own backing field.

List<Parameter> _to;
public List<Parameter> To
{
    get
    {
        if (_to != null && _to.Count == 0) return null;
        return _to;
    }
    set { _to = value; }
}
Mikael Svenson
I believe that OP wanted to *differentiate* null and empty lists.
Groo
Unfortunately, in my case, empty list and null list are different.
Aen Sidhe
That makes it more complicated right away :(
Mikael Svenson
A: 

If you really need that a collection is deserialized to null when no values are provided you can do it by not providing a set accessor, like this:

public class Command
{
    private List<Parameter> to;

    public List<Parameter> To { get { return this.to; } }
}
João Angelo
Bad choice. "To" will be always null after deserialization.
Aen Sidhe
+2  A: 

Ugh, annoying isn't it. You can see it being doing by running sgen.exe on your assembly with the /keep and /debug options so you can debug the deserialization code. It looks roughly like this:

global::Command o;
o = new global::Command();
if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
global::System.Collections.Generic.List<global::Parameter> a_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
// code elided
//...
while (Reader.NodeType != System.Xml.XmlNodeType.EndElement && Reader.NodeType != System.Xml.XmlNodeType.None) {
  if (Reader.NodeType == System.Xml.XmlNodeType.Element) {
    if (((object)Reader.LocalName == (object)id4_To && (object)Reader.NamespaceURI == (object)id2_Item)) {
      if (!ReadNull()) {
        if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
        global::System.Collections.Generic.List<global::Parameter> a_0_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
        // code elided
        //...
      }
      else {
        // Problem here:
        if ((object)(o.@To) == null) o.@To = new global::System.Collections.Generic.List<global::Parameter>();
        global::System.Collections.Generic.List<global::Parameter> a_0_0 = (global::System.Collections.Generic.List<global::Parameter>)o.@To;
      }
    }
  }
  Reader.MoveToContent();
  CheckReaderCount(ref whileIterations1, ref readerCount1);
}
ReadEndElement();
return o;

No less than 3 places where it makes sure the @To property isn't null. The first one is somewhat defensible, hard to deserialize data when the structure doesn't exist. The second one does the null test again, that's the only real good one. The third one is the problem, ReadNull() returned true but it still creates a non-null property value.

If you want to differentiate between empty and null then you have no good solution but edit this code by hand. Do this only if you are really desperate and the class is 100% stable. Well, don't do it. João's solution is the only good one.

Hans Passant
Thank you. I'll add flags.
Aen Sidhe