Sunday 3 October 2010

empty XmlNodeList and avoiding a null check

Now, why does XmlNode.SelectNodes(string xpath) return null if it doesn’t find anything?

I have some existing code that reads some parameters from XML, and passes some objects and those parameters into a method that filters those objects into two sets – one that is sensitive in some way to those parameters, and one that’s not.

FilterProducts(
    XmlNodeList processes, 
    XmlNodeList processAttributes, 
    out List<Product> sensitiveFolio,
    out List<Product> nonSensitiveFolio);

I’m drawing the list of processes and processAttributes from XML – and in some cases people don’t define any attributes, wanting to just build a folio sensitive to a list of processes. In this case the XmlNodeList returned is null – and when I pass that to FilterProducts it blows up.

After the null pattern, then, I figured what I wanted to do was actually pass an empty XmlNodeList rather than a null. FilterProducts could then just pass over the attributes, and work out what was dependent on the processes only. Then I realised that what I really needed to do was dig through FilterProducts and refactor it because it was doing too much – at least compared to the usage pattern was now different to that which originally formed it.

However; both these can happen in sequence. I could deliver the simpler – using the existing code and an empty list, which wouldn’t affect any other code, then taking a look at refactoring FilterProducts, which might affect other parts of the codebase. So, populate processAttributes with a successully parsed XmlNodeList, or a new EmptyNodeList if that's null:

XmlNodeList processAttributes = 
    myConfigNode.SelectNodes("/processAttributes") ?? new EmptyNodeList; 

and then defined EmptyNodeList by subclassing XmlNodeList:

private class EmptyNodeList : XmlNodeList 
{ 
    public override XmlNode Item(int index) 
    {
        throw new NotImplementedException(); 
    }

    public override IEnumerator GetEnumerator() 
    { 
        return new EmptyNodeListEnumerator(); 
    }

    public class EmptyNodeListEnumerator : IEnumerator 
    { 
        public bool MoveNext() 
        { 
            return false; 
        }

        public void Reset() 
        { 
            throw new NotImplementedException(); 
        }

        public object Current 
        { 
            get 
            { 
                throw new NotImplementedException(); 
            } 
        } 
    }

    public override int Count 
    { 
        get 
        { 
            return 0; 
        } 
    }     
}

That works fine, delivering some code, and leaving a look at refactoring some code used in multiple places for later.

2 comments:

Dick de Jong said...

could it be static class?

Tim Barrass said...

Not for this use case -- you can't instantiate a static class.