views:

264

answers:

2

I'm working with an xml fragment, and finding that I'm doing the following a lot:

dim x = xe.Element("foo").Element("bar").Element("Hello").Element("World").Value

however I can't always guarantee that the xml document will contain foo or bar. Is there a nicer way to do this sort of thing without having to null check every query?

i.e.

dim x = ""
if xe.Element("foo").Any() then
    if xe.Element("foo").Element("bar").Any() Then
        if xe.Element("foo").Element("bar").Element("Hello").Any() Then
            x = xe.Element("foo").Element("bar").Element("Hello").Element("World").ValueOrDefault()
        End If
    End If
End If

(ValueOrDefault is an extension method I've added)

A: 

might require modifying your valueOrDefault extension method a bit. Basic idea:

xe.Elements("foo").Elements("bar").Elements("Hello").Elements("World").FirstOrDefault();
Jimmy
This won't help in the case where the `xe` doesn't have any `foo` elements, or `foo` doesn't have any `bar` elements, and so on, will it? The problem the OP has isn't with getting the value of `"World"`, but the value of `Elements("foo")` when there are no foos. I'm guessing the `Elements()` method returns null when it doesn't find a `"foo"`, which causes a NullPointerException. So, I don't think just calling `FirstOrDefault()` at the end will solve the problem. But, I might be wrong.
Benny Jobigan
Elements is an extension method, which returns XElement.EmptySequence when its callee is null.
Jimmy
Hmm, yea. I just looked it up. I was doing something like this once and using a LINQ query instead of using the methods directly, and I had some kind of error when trying to process an element without a particular child element. Hmm.. oh well, maybe I was doing something else that caused the error.
Benny Jobigan
Off the top of my head, XElement.Elements is not an extension, and so can throw. IEnumerable<XElement>.Elements is an extension, and includes the null-check.
Jimmy
+1  A: 

Essentially, you're over analysing the problem.

Start with this:

xe.Elements("foo")

this will return a sequence of all <foo> children of xe; this might be an empty sequence, but will never be null.

Now, extend to this:

xe.Elements("foo")
    .Elements("bar")

This uses extension method Elements() (part of the framework) to look for all <bar> children of the <foo> elements you have so far.

Repeat this the whole way down, until you find the element with a value. Then, use a cast to extract the value:

dim x 
    = (string) xe.Elements("foo")
        .Elements("bar")
            .Elements("Hello")
                .Elements("World")
                    .FirstOrDefault()

Again, the cast is provided by the framework.

All of the checking for null is already handled for you by the smart coders who wrote the framework - this is a large part of what makes XDocument and friends so nice to code with.

Bevan
hmmm this is a better answer than mine :) +1
Jimmy
Not sure if this is just a difference btw C# and VB.Net, but this fails compilation in VB.net, Element is not a member of IEnumerable(XElement), which makes sense. Will have a play when I have more time. Thanks.
hitch
Sorry, that's a typo - should have been Elements at the end. Edited.
Bevan
Thanks for this - it's great that the casting takes care of some of the hard work - wasn't aware that this was possible.In VB.Net this would be:dim x = cstr(xe.Elements("foo").Elements("bar").Elements("hello").Elements("world").FirstOrDefault())note that CType(..., string) and xe....FirstOrDefault().tostring() will give an "Object not set to an instance of an object" error.
hitch