views:

196

answers:

2

I have a base collection class and a child collection class, each of which are serializable. In a test, I discovered that simply having the child class's ReadXml method call base.ReadXml resulted in an InvalidCastException later on. First, here's the class structure:

Base Class

// Collection of Row objects
[Serializable]
[XmlRoot("Rows")]
public class Rows : IList<Row>, ICollection<Row>, IEnumerable<Row>,
    IEquatable<Rows>, IXmlSerializable
{
    public Collection<Row> Collection { get; protected set; }

    public void ReadXml(XmlReader reader)
    {
        reader.ReadToFollowing(XmlNodeName);
        do
        {
            using (XmlReader rowReader = reader.ReadSubtree())
            {
                var row = new Row();
                row.ReadXml(rowReader);
                Collection.Add(row);
            }
        } while (reader.ReadToNextSibling(XmlNodeName));
    }
}

Derived Class

// Acts as a collection of SpecificRow objects, which inherit from Row.  Uses the same
// Collection<Row> that Rows defines which is fine since SpecificRow : Row.
[Serializable]
[XmlRoot("MySpecificRowList")]
public class SpecificRows : Rows, IXmlSerializable
{
    public new void ReadXml(XmlReader reader)
    {
        // Trying to just do base.ReadXml(reader) causes a cast exception later
        reader.ReadToFollowing(XmlNodeName);
        do
        {
            using (XmlReader rowReader = reader.ReadSubtree())
            {
                var row = new SpecificRow();
                row.ReadXml(rowReader);
                Collection.Add(row);
            }
        } while (reader.ReadToNextSibling(XmlNodeName));
    }

    public new Row this[int index]
    {
        // The cast in this getter is what causes InvalidCastException if I try
        // to call base.ReadXml from this class's ReadXml
        get { return (Row)Collection[index]; }
        set { Collection[index] = value; }
    }
}

And here's the code that causes a runtime InvalidCastException if I do not use the version of ReadXml shown in SpecificRows above (i.e., I get the exception if I just call base.ReadXml from within SpecificRows.ReadXml):

TextReader reader = new StringReader(serializedResultStr);
SpecificRows deserializedResults = (SpecificRows)xs.Deserialize(reader);
SpecificRow = deserializedResults[0]; // this throws InvalidCastException

So, the code above all compiles and runs exception-free, but it bugs me that Rows.ReadXml and SpecificRows.ReadXml are essentially the same code. The value of XmlNodeName and the new Row()/new SpecificRow() are the differences. How would you suggest I extract out all the common functionality of both versions of ReadXml? Would it be silly to create some generic class just for one method? Sorry for the lengthy code samples, I just wanted to provide the reason I can't simply call base.ReadXml from within SpecificRows.

A: 

Why are you casting when the Collection can only contain Row objects? Maybe I'm missing something.

Update:

Collection[index] as Row;

Would get around the exception but it might still not result in what you want (ie: a null instead of a Row)

Update:

Would refactoring the base to:

    public void ReadXml<T>(XmlReader reader) where T : IRow
    {
        reader.ReadToFollowing(XmlNodeName);
        do
        {
            using (XmlReader rowReader = reader.ReadSubtree())
            {
                var row = default(T);
                row.ReadXml(rowReader);
                Collection.Add(row);
            }
        } while (reader.ReadToNextSibling(XmlNodeName));
    } 

work for you?

John Bergess
Updated question. `SpecificRows` acts as a collection of `SpecificRow` objects and `SpecificRow : Row`.
Sarah Vessels
Yeah, I tried the `as` operator and didn't get the InvalidCastException, but I still got an exception when I got `null` instead of the `Row` I was expecting.
Sarah Vessels
But yeah, I've fixed that problem--the cast isn't the question I'm asking. I want to refactor the working `ReadXml` methods to where they still work but don't duplicate so much code.
Sarah Vessels
Oh I understand now, I might have an idea that will help with that.
John Bergess
`default(T)` will return `null` if `T` is a reference type
Dave
A: 

It looks like the only difference is this line:

var row = new Row();

vs

var row = new SpecificRow();

So you could extract that into a virtual function, say, MakeRow(), and then ReadXml() can be without duplication.

Carl Manaster
The value of `XmlNodeName` will differ between base and child, too. I like your idea, but what should the return type of `MakeRow` be?
Sarah Vessels