tags:

views:

1318

answers:

5

I frequently have code that looks something like this:

if (itm != null)
{
    foreach (type x in itm.subItems())
    {
        //dostuff
    }
}
//do more stuff

In situations where //do more stuff is omitted, it is very easy to avoid the extra foreach loop. By exitting scope using the appropriate command (depending on what is going on, this generally would mean a return statement or a continue statement).

This type of thing tends to result in arrow code. I currently have a few ways to deal with this:

  • Use code like itm = itm == null ? itm.subItems() : emptyArray
  • Allow arrow code
  • Use goto
  • Use evil scoping hacks (wrapping the whole thing, if statement in all, in a scope and then breaking out of it). In my opinion evil scoping hacks are basically equivalent to goto except uglier and harder to read, so I don't consider this a valid solution.
  • Refactor some of the chunks into new methods. There are in fact a few cases where this probably is a good solution, but mostly it's not appropriate since the null references are mainly error conditions from MS-functions.

Anyone care to offer a response on what approaches are considered preferable?

+2  A: 

I like less nesting, for me it reads better. No goto please :)

I keep methods short, so it is usually a return for that scenario.

if (itm == null) return;
foreach (type x in itm.subItems())
{
   //dostuff
}

If the more stuff is needed, are simple statements and can be done before the foreach, you can:

if (itm == null)
{
   //do more stuff 
   return;
}
foreach (type x in itm.subItems())
{
   //dostuff
}

If the above is not the case, it is likely the method is too long and some of it would be moved away anyway. Probably:

if( itm != null ) SomeActionOnSubItems(itm.subItems);
// do more stuff (can be some method calls depending on level of abstraction).
eglasius
You're ignoring the "Do more stuff" step in my example. Your solution is already mentioned as a solution for when I don't have "Do more stuff."
Brian
@Brian it varies based on what we are doing, updated with more scenarios, resharper helps moving it around super fast :)
eglasius
+1  A: 

First writing the code this way is very performant than most hacks. If you're looking for performance improvement don't bother.

If you're looking a way to accomplish this in a shorter way, depending on how big is foreach content you can do a:

if(itm != null)
{
  itm.SubItems().ForEach(x => do something with x ...);
}
ssg
A: 

Personally, I'd probably leave the structure the way you have it.

The first option (itm = itm == null ? itm.subItems() : emptyArray) seems less nasty than the others, but I still like your original better.

The problem is, from another developer's perspective, anything else is going to make your code less obvious. If there is a foreach running through a collection, I expect that the collection will (at least normally) have items contained in there. If the collection could be empty, that's not going to be obvious to other people without comments (which take longer to write than the if check).

Doing any of the hacks to avoid the if check just seems like you're trying to be too clever.

Reed Copsey
My issue is that in some cases the code becomes hard to read because of all the if statements. Sure, another developer might have trouble reading it. But as it is, even *I* have trouble reading it, never mind another developer.
Brian
+12  A: 

If you're using C# 3, you could always write an extension method:

public static IEnumerable<SubItem> SafeSubItems(this ItemType item)
{
     return item == null ? Enumerable.Empty<SubItem> : source.SubItems();
}

Then just write:

foreach (SubItem x in itm.SafeSubItems())
{
    // do stuff
}
// do more stuff

The key thing is that extension methods can be called even "on" null references.

What would be nice would be a "null-safe dereferencing" operator, so we could write:

// Not valid C# code!
foreach (SubItem x in itm?.SubItems() ?? Enumerable.Empty<SubItem>())
{
}

Or just define an EmptyIfNull extension method on IEnumerable<T> and use

// Not valid C# code!
foreach (SubItem x in (itm?.SubItems()).EmptyIfNull())
{
}
Jon Skeet
that's very kewl Jon! I have an extension method to return a list or null (if the list is null) to save me having the if check, then the return data => .ToListIfNotNullOrEmpty(). Same concept :) I'm going to use you idea! Thanks heaps!
Pure.Krome
I like this solution. It reduces repetition of nullity checks which means it's worthwhile if enough null collections are being produced. In my case, that's probably true. A shame I'm currently using C# 2, but this is something to keep in mind once I update.
Brian
I guess I can use this solution with a function instead of an extension method for now if need be.
Brian
Or I can install LINQ Preview ( http://download.microsoft.com/download/4/7/0/4703eba2-78c4-4b09-8912-69f6c38d3a56/linq%20preview.msi ).
Brian
Hmmm. I showed this technique to a coworker and his response was that calling an extension method of a potentially null object was highly counterintuitive and thus confusing. So, I don't think this technique will ever make it into any of my production code.
Brian
Ask him how he'd feel if String.IsNullOrEmpty had been an extension method instead, and how readable "if (x.IsNullOrEmpty())" is vs "if (string.IsNullOrEmpty(x))"...
Jon Skeet
I bet he'd like the 2nd one better.
Brian
+3  A: 

You could use the Coalesce operator (coded as a double question mark, ??, .net 2 upwards). This'll return the first non-null value in a list of values, so in this snippet...

MyClass o1 = null;
MyClass o2 = new MyClass ();
MyClass o3 = null;
return o1 ?? o2 ?? o3;

...o2 would be returned.

So you could re-code your original code sample as

foreach (type x in (itm ?? emptyArray).subItems())
{
    //dostuff
}

//do more stuff

However, personally I don't mind the nesting. It's instantly clear what's going on. I find the Coalesce operator a little harder to read, and that little nest is a small price to pay for clarity.

sgreeve
The problem isn't if itm.subItems() is null - it's if itm is null, in which case this code blows up.
Jon Skeet
Fair point - edited my code accordingly.
sgreeve
@sgreeve the problem is actually how that reads, itm ?? emptyArray is really weird, as in emptyArray you actually need a defaultForItmClassWithAnArray ... and that moves you to get an ItmClass instance just for its emptyArray - I don't think it is worth it
eglasius
@Mark careful, it doesn't work as it read
eglasius
@Freddy Rios - note that I was refactoring the code sample posted in the original question, which included the "emptyArray", and not at all suggesting this will work verbatim.
sgreeve