views:

203

answers:

4

Below is some linqpad test code. When this runs it errors because the second instance of "item" has a null list of subitems as opposed to an empty list.

I want to treat both situations (null or empty list) in exactly the same way but I wondered if there was a cleaner way than just putting a null check on the list and initialising an empty list when there's a null.

in other words, I could do this:

from si in (i.subitems == null ? new List<item>() : i.subitems)

but that's a little ugly and I wondered how I could improve on that?

public class item
{
    public string itemname { get; set; }
    public List<item> subitems { get; set; }
}

void Main()
{
    List<item> myItemList = new List<item>() 
    {
        new item 
        {
            itemname = "item1",
            subitems = new List<item>()
            {
                new item { itemname = "subitem1" },
                new item { itemname = "subitem2" }
            }
        },
        new item 
        {
            itemname = "item2"
        }
    };

    myItemList.Dump();

    var res = (from i in myItemList
            from si in i.subitems
            select new {i.itemname, subitemname = si.itemname}).ToList();

    res.Dump();
}

as a bonus question, can this same linq query be represented as a lambda and treat nulls the same way?

Cheers, Chris

+7  A: 
from si in (i.subitems ?? new List<item>())

how about that?

hunter
yeah, that's better than mine (and I'm kicking myself for not doing that in the first place) but it still means creating an object simply to eliminate it which feels bad
Chris Simpson
Bonus points for finding a good use for ??
CaptnCraig
@captncraig For other uses of ??, see http://stackoverflow.com/questions/1689530/how-useful-is-cs-operator/1689544#1689544
Marcel Gosselin
+2  A: 

You could add an (evil) extension method to do the work for you

public static IEnumerable<T> EnsureNotEmpty<T>(this IEnumerable<T> enumerable) {
  if ( enumerable == null ) {
    return Enumerable.Empty<T>();
  } else { 
    return enumerable;
  }
}
JaredPar
Is `Enumerable.Repeat<T>(0)` preferable to `Enumerable.Empty<T>()`?
bdukes
@bdukes, Enumerable.Empty is better as it's more declarative of your intention. For some reason though I keep forgetting it's a part of the framework and do Repeat(0) instead.
JaredPar
The method should be called `EnsureNotNull(...)`. Because `EnsureNotEmpty(...)` sounds like it would add a deliberate item. :->
herzmeister der welten
+6  A: 

You could use the null coalescing operator

var res = (from i in myItemList
           from si in i.subitems ?? new List<item>()
           select new { i.itemname, subitemname = si.itemname }).ToList();

But I think you should just filter the empty ones out

var res = (from i in myItemList
           where i.subitems != null
           from si in i.subitems
           select new { i.itemname, subitemname = si.itemname }).ToList();

As for a lambda version you could say

var res = myItemList.Where(x => x.subitems != null)
                    .SelectMany(
                        x => x.subitems.Select(
                            y => new { x.itemname, subitemname = y.itemname }
                        )
                     );

But the query syntax version is way more readble.

Jason
actually that second option is very readable and doesn't mean a new list needs to be created only to be ignored. thanks
Chris Simpson
@Chris Simpson: I added lambda version since you asked for it. The query syntax version is way more readable.
Jason
I really think the where clause is the cleanest solution here so I'm marking this as the answer. I was just curious about the lambda equivalent but I agree, it's not as readable. Thanks.
Chris Simpson
A: 

An additional method would be not to allow subitems to be null. You could make the item constructor so that it defaults subitem to an empty list and then not allow null in the subitem setter.

That of course assumes you have access to modify item.

The null coalescing operator is what you are looking for as pointed out by Hunter Daley

Brandon Bodnár