Reputation: 5002
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
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
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
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
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