bwall
bwall

Reputation: 1060

C# Null References while serializing

When serializing, how do you handle backer variables? Mine keep on turning up null, as in the following example:

[DataContract]
public Person
{
     public Person()
     {
         //a break point here doesn't get called in time - I try to use _myList and get an exception first. :(
         _myList = new string[3];
     }

    private string[] _myList {get; set;} = new string[3];  // Will be filled: {"Bob", "The", "Builder"};

    [DataMember]
    public string Occupation
    {
        get { return _myList[2]; }
        set {_myList[2] = value; }
    }

}


SerializeFunction(Person Bob, string filePath = @"C:\Temp\BobTheBuilder.xml")
{
     DataContractSerializer serializer = new DataContractSerializer(typeof(Person));
     using(XmlWriter xmlwriter = XmlWriter.Create(filePath))
     {
         serializer.WriteObject(xmlWriter, bob);
         xmlWriter.Flush();
     }
     using(XmlReader reader = XmlReader.Create(filePath))
     {
         // This throws an exception, because _myList is null
         Person readInBob = deserializer.ReadObject(xmlReader);
     }        
}

I get an exception when I try to read Bob back in, because the backer property _myList doesn't get initialized. How do you guys handle situations like this?

Upvotes: 1

Views: 92

Answers (2)

bwall
bwall

Reputation: 1060

If you implement IXmlSerializable, you gain the control to do whatever you need to. Replace the person class above with:

public Person : IXmlSerializable
{
    private string[] _myList {get; set;} = new string[3];

    public string Occupation
    {
        get { return _myList[2]; }
        set {_myList[2] = value; }
    }

    XmlSchema IXmlSerializable.GetSchema() => null;

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
         //THIS IS THE KEY LINE:
         _myList = new string[3];// you can do any initialization you need before actually reading.

        while(reader.Read())
        {
            if(reader.NodeType != XmlNoteType.Element)
                continue;

            string propertyName = reader.Name; // we have arrived at an element.
            reader.Read(); // we have the property name, now get it's value

            switch(propertyName)
            {
                case nameof(Occupation):
                    Occupation = reader.Value; // for an int, you could do: int.Parse(reader.Value);
                    break;
                case "ComplicatedClass": // if you need to serialize complicated class member then you could do this:
                    // you'd have to not do the reader.Read() above...
                    // DataContractSerializer complicatedClassSerializer = new DataContractSerializer(typeof(ComplicatedClass));
                    // ComplicatedClass foo = complicatedClassSerializer.ReadObject(reader) as ComplicatedClass;
                    break;
            }
        }
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
         writer.WriteElementString(nameof(Occupation), Occupation.ToString());

         //for a more complicated  type than string, int etc:
         //DataContractSerializer complicatedClassSerializer = new DataContractSerializer(typeof(complicatedClass));
         //complicatedClassSerializer.WriteObject(writer, new ComplicatedClass());
    }
}

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1062745

When you do new List<int>(3), you are setting the initial capacity, not the number of elements. There are still zero elements, so _myList[0] etc will all fail until you Add the appropriate number of elements. This contrasts to arrays, as new int[3] always has exactly 3 elements (which will be null initially).

Frankly, there's no need for a list here at all, if you are trying to hard-code them to positions - just use simple properties:

[DataMember]
public string Occupation {get;set;}

Upvotes: 1

Related Questions