Animesh D
Animesh D

Reputation: 5002

Where predicate behavior when no element matches the condition

When I try this:

Month1Value = 
    g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth)
    .FirstOrDefault()
    .Value

if there is data in the last month, I get the Value assigned just fine. If there is no value I get this exception:

Object reference not set to an instance of an object

However, if I change the above to:

Month1Value = 
     g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth)
     .Select(x=>x.Value)
     .FirstOrDefault()

I get a zero, like I want, if there is no matching element.

What is the difference between these two ways of writing the lambda expression?

SSCCE:

void Main() {
    var currentMonth = DateTime.Now.Month;
    var currentTimeStamp = DateTime.Now;

    int lastMonth = currentTimeStamp.AddMonths(-1).Month; // 9

    System.Globalization.CultureInfo cInfo = new System.Globalization.CultureInfo("en-US");
    System.Globalization.DateTimeFormatInfo english = cInfo.DateTimeFormat;
    var cult = System.Globalization.CultureInfo.InvariantCulture;
    string fmt = "yyyyMM";

    var DataResults = new[] {
        new Data() {Desc="Item Name", Seq=10639, Period="200906", Value=1.65M},
        new Data() {Desc="Item Name", Seq=10639, Period="200907", Value=1.56M},
        new Data() {Desc="Item Name", Seq=10639, Period="200905", Value=1.62M},
        new Data() {Desc="Item Name", Seq=10639, Period="200908", Value=1.6M}
    };

    var pivotedResults =
        DataResults
        .GroupBy(i => new { i.Desc, i.Seq })
        .Select((g, k) => new PivotedData
        {
            Desc = g.Key.Desc,
            Seq = g.Key.Seq,
            // Month1Value = g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth).FirstOrDefault().Value,
            Month1Value = g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth).Select(x => x.Value).FirstOrDefault(),
            Month1 = english.GetAbbreviatedMonthName(lastMonth),
        }).ToList();    

    DataResults.Dump();
    pivotedResults.Dump();
}

public class Data {
   public string Desc { get; set; }
   public long Seq { get; set; }
   public string Period { get; set; }
   public decimal Value { get; set; }
}

public class PivotedData {
   public string Desc { get; set; }
   public long Seq { get; set; }
   public string Month1 { get; set; }
   public decimal? Month1Value { get; set; }
}

Upvotes: 0

Views: 410

Answers (4)

julealgon
julealgon

Reputation: 8162

FirstOrDefault will enumerate the results and return 'null' when there is no element matching the criteria, since default(T) for reference types is null. Then, when you call .Value on the result, you get a NullReferenceException.

On the other hand, by using Select you are not yet enumerating the sequence, and thus it will get the value only for those that match rest of the query, so that is why you get the correct behavior (i.e. only the values that match the where clause are considered).

Upvotes: 3

David Pilkington
David Pilkington

Reputation: 13620

Month1Value = 
    g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth)
    .FirstOrDefault()
    .Value

tries to access the result of ".FirstOrDefault()" which is nullable. Therefore if it is null, it throws the null ref.

Month1Value = 
     g.Where(i => DateTime.ParseExact(i.Period, fmt, cult).Month == lastMonth)
     .Select(x=>x.Value)
     .FirstOrDefault()

selects the values first and then gets the first. It is still possible for the result to be null but because you are not calling ".Value" on it, no exception is thrown.

While they may look the same, the two examples are fundamentally different.

Upvotes: 1

Kris Vandermotten
Kris Vandermotten

Reputation: 10201

The first line says: "take the first i from the last month, and then take its value."

The problem with that is that if there is no i from last month, then you cannot take its value because it doesn't exist.

The second line says "take the first value of an i from last month, or the default value (0) if there is none".

This does not have that problem.

See other answers for a more technical version of the same answer.

Upvotes: 1

Martin Moser
Martin Moser

Reputation: 6269

Well the answer is quite simple.

In the first version you have a call after .FirstOrDefault(). And FirstOrDefault() returns null (see msdn) for objects, if there is no element. And then you try to call .Value on a null pointer.

In the second version it's the last call, before that the Select() turns the enumerable into an int-enumerable and the default for int is 0.

Upvotes: 6

Related Questions