2

I was working on some code when I discovered something I don't fully understand so I created the following test code:

static void Main(string[] args) { string myKey = "bla"; var list = new List<KeyValuePair<string, string>>(); var list2 = new List<Test>(); var bla1 = (from a in list where a.Key == myKey select a.Value).FirstOrDefault(); var bla2 = list.FirstOrDefault(x => x.Key == myKey).Value; var bla3 = (from a in list2 where a.Key == myKey select a.Value).FirstOrDefault(); var bla4 = list2.FirstOrDefault(x => x.Key == myKey).Value; } struct Test { public string Key { get; set; } public string Value { get; set; } public Test(string key, string value) { Key = key; Value = value; } } 

Now as long as Test is a struct all of the 4 bla-lines work fine. But as soon as I change Test to be a class instead of a struct bla4 fails. How can this be? Isn't bla4 just another (lambda) way of doing bla3 and therefore should behave the same? Why does it make a difference whether the list contains a struct's or classes? And is there a way to have a lambda version of bla4 that doesn't throw an exception and instead returns null like the others?

9
  • Can you do ?.Value instead of .Value ? Commented Apr 13, 2017 at 9:10
  • 5
    The default of a class type is null. The default of a struct is an empty struct. That's why FirstOrDefault(..).Value will fail with a NullReferenceException Commented Apr 13, 2017 at 9:11
  • Unfortunately no as this context doesn't yet allow C#6 code.. Commented Apr 13, 2017 at 9:11
  • @MemphiZ ah too bad.. Commented Apr 13, 2017 at 9:12
  • @PanagiotisKanavos Please consider converting your comment into an answer. Commented Apr 13, 2017 at 9:12

3 Answers 3

2

A struct can never be null.

Therefore your call to FirstOrDefault() will actually cause a new struct to be constructed, because default(struct) is effectively new(struct).

However if you change it to a class, then default(class) is null.

Thus the result of your call

list2.FirstOrDefault(x => x.Key == myKey) 

will actually result in null being returned once it is a class. You should therefore recieve a NullReferenceException when trying to access .Value.

In layman terms what your bla3 acutally does is check if there is a matching item within the collection. If there is, it will select the property Value of that item, otherwise it will return the default of that property's type.

Your bla4 instead looks if it finds an item that matches the Key property. If it does, it selects the first of those items, if it does not, it creates a default of that item's type and returns that. You then try to acces that return value's Value property.

Sign up to request clarification or add additional context in comments.

Comments

1

The default of a class type is null. The default of a struct is an empty struct. That's why FirstOrDefault(..).Value will fail with a NullReferenceException.

You should check that FirstOrDefault() did return something before trying to access the result.

var item = list2.FirstOrDefault(x => x.Key == myKey); var bla4 = (item == null)?null:item.Value; 

In C# 6 and later you can use the ?. operator, to perform the same check:

var item = list2.FirstOrDefault(x => x.Key == myKey)?.Value; 

Another option is to use ?? to replace null with a default value.

var item = list2.FirstOrDefault(x => x.Key == myKey) ?? new Test(); var bla4 = item.Value; 

It's common to create a special Empty object to use in such cases, eg:

class Test { //... public static readonly Empty =new Test(); } var item = list2.FirstOrDefault(x => x.Key == myKey) ?? Test.Empty; var bla4 = item.Value; 

Comments

1

Isn't bla4 just another (lambda) way of doing bla3 and therefore should behave the same?

No. bla3 extracts some strings from the objects contained in a list. You then call FirstOrDefault on that set of strings and obtain a null value, because that set was empty.

bla4 calls FirstOrDefault directly on the set of objects and you then obtain the string property Value from the defaulted object.

When the objects contained in your lists are structs, then the defaulted value will contain a null Value property. That's why they superficially seem similar. But be very clear - the order in which Value is being accessed and FirstOrDefault is being called is different, and FirstOrDefault is operating on different types of things.

Of course, exactly the same can be said for bla2 vs bla1.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.