2
var result = foo.FirstOrDefault(f => f.bar == barVal).someProperty

This will not work if there is no match (default is null) - trying to access a property on a null object. We can rewrite as follows:

var result = foo.Where(f => f.bar == barVal)
                .Select(f => f.someProperty).DefaultIfEmpty(0).First()

Whilst it works, this doesn't seem like the most elegant way to do this... is there a better way?


Of course one can do something such as:

var result = 0;
var tmp = foo.FirstOrDefault(f => f.bar == barVal);
if(tmp != null) result = tmp.someProperty

But in a more complex query this approach looks to be even 'messier' than the DefaultIfEmpty approach

var tmpSet = dataSet.GroupBy(f => f.ID);
var newSet = tmp.Select(f => new { 
               ID = f.ID,
               SomeProperty = f.Where(g => g.bar == barVal)
                               .Select(f => f.SomeProperty)
                               .DefaultIfEmpty(0).First()
               });
Vok
  • 457
  • 5
  • 19
  • 3
    If someProperty is integer, then you can use `FirstOrDefault()` instead of `DefaultIfEmpty(0).First()` – Sergey Berezovskiy Apr 09 '14 at 11:18
  • Specify _elegant_, i find it elegant whilst it's a little bit verbose (apart from the fact that you don't need to use `DefaultIfEmpty(0)` when it's already the default value). – Tim Schmelter Apr 09 '14 at 11:19
  • Good point - I think using an integer was a bad example to illustrate the question – Vok Apr 09 '14 at 11:44

3 Answers3

5

You can do that:

var result = foo.Where(f => f.bar == barVal)
                .Select(f => f.someProperty)
                .FirstOrDefault();

Or you can write a custom extension method:

public static TResult IfNotNull<TSource, TResult>(this TSource instance, Func<TSource, TResult> getter, TResult defaultValue = default(TResult))
    where TSource : class
{
     if (instance != null)
          return getter(instance);
     return defaultValue;
}

...

var result = foo.FirstOrDefault(f => f.bar == barVal)
                .IfNotNull(f => f.someProperty);

EDIT: and with C# 6, you'll be able to write this:

var result = foo.FirstOrDefault(f => f.bar == barVal)?.someProperty ?? 0;

See this discussion on the Roslyn Codeplex site for details.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • I shouldn't have used a standard default value in the example, although FirstOrDefault() is indeed an improvement on the DefaultIfEmpty(0).First(). Looks like the custom extension is the way to go - and the example there that can take a custom default value looks great. – Vok Apr 09 '14 at 11:51
1

I asked a similar question a while ago; the best way i found was to use either the default value that comes from the FirstOrDefault or use the DefaultIfEmpty for non-default values. Just dereference the property in a Select Linq query first

So I don't really see any better way to dereference the property. Extension methods are the only way to collapse this kind of behavior into a more expressive name.

Community
  • 1
  • 1
samy
  • 14,832
  • 2
  • 54
  • 82
0

The most conscious solution right now seems to be an extension method.

var foo = "foo";
var fooNull = (String)null;

var fooLength = foo.Get(_ => _.Length, -1);         //  3
var fooNullLength = fooNull.Get(_ => _.Length, -1); // -1

var bar = new[] { "bar" };
var barNull = new[] { (String)null };

var barLength = bar.GetSingle(_ => _.Length, -1);         //  3
var barNullLength = barNull.GetSingle(_ => _.Length, -1); // -1

var baz = new[] { null, "bar", null };

var bazLength = baz.Get(_ => _.Length, -1); // { -1, 3, -1 }

I would implement this as follows, error handling left out for conciseness. Note that I here used more meaningful but longer method names than in the example above.

// Get the value of a property of an object or a default
// value if the object is null.
public static TProperty GetGetPropertyValueOrDefault<TObject, TProperty>(this TObject obj, Func<TObject, TProperty> propertyValueGetter, TProperty defaultValue = default(TProperty))
   where TObject : class
{
   return (obj != null) ? propertyValueGetter(obj) : defaultValue;
}

// Get the value of a property for the single object in a
// sequence or a default value if the sequence is empty.
public static TProperty GetSinglePropertyValueOrDefault<TObject, TProperty>(this IEnumerable<TObject> sequence, Func<TObject, TProperty> propertyValueGetter, TProperty defaultValue = default(TProperty))
   where TObject : class
{
    return sequence.SingleOrDefault().GetGetPropertyValueOrDefault(propertyValueGetter, defaultValue);
}

// Get the value of a property or a default value if the
// object is null for all objects in a sequence.
public static IEnumerable<TProperty> GetGetPropertyValuesOrDefault<TObject, TProperty>(this IEnumerable<TObject> sequence, Func<TObject, TProperty> propertyValueGetter, TProperty defaultValue = default(TProperty))
   where TObject : class
{
    return sequence.Select(element => element.GetGetPropertyValueOrDefault(propertyValueGetter, defaultValue));
}
Daniel Brückner
  • 59,031
  • 16
  • 99
  • 143