17

For brevity's sake in my code, i'd like to be able to do the following: having a collection, find the first element matching a lambda expression; if it exists, return the value of a property or function. If it doesn't exist, return null.

Updated examples w. classes

Let's have a collection of stuff

class Stuff
{
    public int Id { get; set; }
    public string Value { get; set; }
    public DateTime? ExecutionTime { get; set; }
}

What I am aiming for is a way to return nicely when calling this

var list = new Stuff[] { new Stuff() { Id = 1, Value = "label", ExecutionTime = DateTime.Now } };

// would return the value of ExecutionTime for the element in the list
var ExistingTime = list.FirstOrDefault(s => s.Value.Contains("ab")).ExecutionTime;

// would return null
var NotExistingTime = list.FirstOrDefault(s => s.Value.Contains("zzz")).ExecutionTime; 

Is it possible with some linq-syntax-fu or do I have to check explicitly for the return value before proceeding?

Original example w. strings

var stuff = {"I", "am", "many", "strings", "obviously"};

// would return "OBVIOUSLY"
var UpperValueOfAString = stuff.FirstOrDefault(s => s.contains("bvi")).ToUpper();

// would return null
var UpperValueOfAStringWannabe = stuff.FirstOrDefault(s => s.contains("unknown token")).ToUpper();

Comment: I shouldn't have used strings in my original example, since it slightly skews the question by centering it on the ToUpper method and the string class. Please consider the updated example

Kris Harper
  • 5,672
  • 8
  • 51
  • 96
samy
  • 14,832
  • 2
  • 54
  • 82
  • 3
    Maybe write some extension method that can be used instead/before the `ToUpper()`? This could e.g. convert `null` to `string.Empty`. – Uwe Keim Jun 27 '12 at 09:03
  • 1
    Well, that's what i was thinking: i can create a Func(fodt => fodt != null ? fodt.property : null); function and wrap the FirstOrDefault call in it, i'd like to know if there's something cleaner – samy Jun 27 '12 at 09:10

3 Answers3

32

Why not just do:

stuff.Where(s => s.contains("bvi"))
     .Select(s => s.ToUpper())
     .FirstOrDefault()

If you have a "non-default default", you can do:

stuff.Where(s => s.contains("bvi"))
     .Select(s => s.ToUpper())
     .DefaultIfEmpty("Something Else")
     .First()
Ani
  • 111,048
  • 26
  • 262
  • 307
  • +1, it's indeed a good way to use only linq extensions. Isn't there though a problem with the where, which is that the whole collection would be analyzed each time this call is made, even if there is an item matching the predicate very early in the collection? – samy Jun 27 '12 at 09:40
  • 2
    @samy No, the final `FirstOrDefault` will stop iteration on the first, if there is a first. None of the linq above requires full-list resolution to work, it is all deferred. – Adam Houldsworth Jun 27 '12 at 09:43
  • 2
    @Samy: No, i've asked [a very similar question](http://stackoverflow.com/questions/10110013/order-of-linq-extension-methods-does-not-affect-performance) a short while ago. See [Eric Lippert's answer](http://stackoverflow.com/a/10110269/284240). – Tim Schmelter Jun 27 '12 at 09:44
  • 1
    That's right. Nothing is iterated until `FirstOrDefault` (or `First`) is called. Then, elements are only iterated in the chain until one is returned. It's efficient in this sense. You can test for yourself by putting a side effect into the `Where` or `Select` and seeing that it only iterates until the first one is matched. – yamen Jun 27 '12 at 09:45
  • 1
    Mind blown! :) @TimSchmelter: thank you very much for this enlightening read. – samy Jun 27 '12 at 09:51
  • @yamen: indeed, that's what i just read in Eric Lippert's answer... I think Ani's solution becomes the best way to do this since it doesn't require writing a new extension method. – samy Jun 27 '12 at 09:53
9

I like this as an extension method:

public static U SelectMaybe<T, U>(this T input, Func<T,U> func)
{
    if (input != null) return func(input);
    else return default(U);
}

And usage:

var UpperValueOfAString = stuff.FirstOrDefault(s => s.Contains("bvi")).SelectMaybe(x => x.ToUpper());
var UpperValueOfAStringWannabe = stuff.FirstOrDefault(s => s.Contains("unknown token")).SelectMaybe(x => x.ToUpper());

This will chain return the default value (null in this case, but as is correct for that type), or call the relevant function and return that.

yamen
  • 15,390
  • 3
  • 42
  • 52
  • Indeed, but it adds value - I am happy to +1 someone who improves the content. – Adam Houldsworth Jun 27 '12 at 09:23
  • Yes, that's very nice and fits nicely in most of my cases; i will perhaps pass the default value as a lambda too but it's simple and uesful. Thanks – samy Jun 27 '12 at 09:27
  • Note I've changed the name to `SelectMaybe` because it's really just a `Select` that checks for `null`. You can add overloads that provide the default value to return, or supply the predicate to test. – yamen Jun 27 '12 at 09:29
4

Update:

Based on the question clarification, you don't need any extra code to achieve what you want. Simply use a where clause and a select projection clause:

var theString = stuff
    .Where(s => s.contains("unknown token"))
    .Select(s => s.ToUpper())
    .FirstOrDefault();


Old Answer

As suggested in the comments (and made generic in another answer), wrap the ToUpper call in an extension method. Extension methods boil down to syntax sugar around static method calls, so they can handle nulls just fine.

static class StringExtensions
{
    public static string PossiblyToUpper(this string s)
    {
        if (s != null)
            return s.ToUpper();

        return null;
    }
}

Your call would become:

var upperValueOfAStringWannabe = stuff
    .FirstOrDefault(s => s.contains("unknown token"))
    .PossiblyToUpper();

It is now just a discussion over whether the extension method to simply support single-line style code is more expressive than multiple lines - at the end of the day, expressing the intent of the code is more important than what the code looks like.

Personally I think extension methods that handle nulls are confusing at first glance, as they are sugared up to look like regular methods.

Community
  • 1
  • 1
Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
  • I never thought about using a static extension method, that's a nice idea that i'll keep in mind. However the Possibly yamen proposes is much more versatile. Thank you anyway – samy Jun 27 '12 at 09:25
  • @samy Not a problem, and yes I agree the generic solution is quite nice. I've already nicked it! :-) – Adam Houldsworth Jun 27 '12 at 09:26
  • 1
    Well it depends on what you mean to "do that for you". The same result can be obtained in a different way. The .Select(x => x.ToUpper()) by using Where before it to filter can do that just fine, and it will uppercase only the returned results, in case when they are empty it will do nothing. and the the FirstOrDefault(). The only bad thing is that uppercasing will uppercase all the filtered items instead of onyl the first. – Marino Šimić Jun 27 '12 at 09:34
  • @MarinoŠimić I've removed that statement. – Adam Houldsworth Jun 27 '12 at 09:37
  • @MarinoŠimić That's what i was asking Ani about in the comments for his answer. It may not look much but since the collection may be very large, i'd like to avoid this overhead. – samy Jun 27 '12 at 09:44
  • @samy I don't see how it would upper-case *all* items, projection is also a deferred action. – Adam Houldsworth Jun 27 '12 at 09:45
  • @AdamHouldsworth: that's what i just read from the comment by Tim under Ani's answer. It's upvote material :) – samy Jun 27 '12 at 09:53