0

I want to access some deep structures in an XML document using XDocument.

To avoid NULL exceptions, so I have many IF checks like those

if(doc.Root.Element("E1") != null)
{
    if(doc.Root.Element("E1").Element("E2") != null)
    { 
        if(doc.Root.Element("E1")
                   .Element("E2")
                   .SingleOrDefault(e => e.Attribute("id") != null && 
                                         e.Attribute("id").Equals("ABC")) != null)
        {
             var n = doc.Root
                        .Element("E1")
                        .Element("E2")
                        .SingleOrDefault(e => e.Attribute("id") != null && 
                                              e.Attribute("id").Equals("ABC"))
                        .Attribute("name").Value;
        }
    }
}

The actual structure is way deeper. Can I eliminate these NULL checks somehow?

juergen d
  • 201,996
  • 37
  • 293
  • 362
  • 3
    Did you check [this](http://geekswithblogs.net/nabuk/archive/2014/03/26/get-rid-of-deep-null-checks.aspx), [this](http://stackoverflow.com/questions/2080647/deep-null-checking-is-there-a-better-way) and [this](http://stackoverflow.com/questions/17672481/cleaner-way-to-do-a-null-check-in-c) – Sriram Sakthivel Aug 13 '14 at 17:29

3 Answers3

1

You can create a simple extension method capable of applying a projection to a possibly null value that propagates null values instead of throwing an exception: (Note that different people like using different names for this operation; feel free to call the method whatever makes the most sense to you.)

public static TResult Use<TSource, TResult>(
    this TSource obj, Func<TSource, TResult> selector)
    where TSource : class
    where TResult : class
{
    return obj == null ? null : selector(obj);
}

With this your code can be condensed into the following:

var name = doc.Root.Element("E1")
        .Use(element => element.Elements("E2"))
        .Use(elements => elements.SingleOrDefault(
            element => element.Attribute("id")
                .Use(att => att.Value) == "ABC"))
        .Use(element => element.Attribute("name").Value);
Servy
  • 202,030
  • 26
  • 332
  • 449
-1

You don't need any check. You can cast the attribute to string then check the equality using == instead of using Equals.

For elements, you can use XPath instead of selecting them separately like Root.Element("Foo").Element("Bar") etc. And then only check the result of the XPath query.That will require less check at least...

Also it is worth noting that in C# 6 there will be a Null propagating operator and which allow you to access members of an object safely, then you won't need any of those checks..

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • 1
    That only gets rid of some of the checks, you still have all of the element checks – Servy Aug 13 '14 at 17:45
  • Not really sure why you're getting down voted - a reasonable answer... with a nice tidbit of information for the future. It kind of demotivates you from wanting to ever assist anyone on here. – Mike Aug 13 '14 at 18:00
-2

You can do this:

            var n = doc.Elements("E1").DefaultIfEmpty()
                    .Elements("E2").DefaultIfEmpty()
                    .FirstOrDefault(e => e!= null && e.Attribute("id") != null && e.Attribute("id").Equals("ABC"));

        if (n != null) {
            string s = n.Attribute("name").Value;
        }
  • This will throw a NRE if there are no E1 or E2 elements. `Elements` is also not a valid method to call on the results of your first `DefaultIfEmpty`. – Servy Aug 13 '14 at 18:06
  • It won't throw a NRE because I set DefaultIfEmpty(), try this code. – Marcos Lora Aug 13 '14 at 18:08
  • 1
    What do you think `DefaultIfEmpty` will return if there are no elements? It'll return a sequence of items with a single `null` value. If you get the first item from that sequence then you'll have `null`, and it'll throw a NRE. Why don't *you* try the code. And on top of all of that, it's semantically different, as you're not getting the first element, you're getting a sequence of elements, which isn't what the OP's code is doing. – Servy Aug 13 '14 at 18:09
  • The collection has a null element, but I check if it is different from null in FirstOrDefaultPart(e => e != null && ... – Marcos Lora Aug 13 '14 at 18:14
  • And if you removed the `DefaultIfEmtpy` calls you could remove that null check as there would never be any null values. But then you still have the problem of not getting just the first element, and instead getting all of the elements. – Servy Aug 13 '14 at 18:15