In the extension methods Single / SingleOrDefault and First / FirstOrDefault have semantic distinction. Though in many places these methods are being used without the semantic consideration, more often they return the expected results. This behavior makes this misuse more common.
Both the Single and First methods have an accompanying helper method named as xxOrDefault which does nothing more than returning the default value of the specified data type if no element matches the query in the collection. For example if it’s a string collection they return null and if it’s an integer collection they return 0.
So the main semantic distinction comes between Single and First
To check this we create 3 collections like the following.
1: private static List<StudentViewModel> list0;
2:
3: private static List<StudentViewModel> list1 = new List<StudentViewModel>()
4: {
5: new StudentViewModel() { StudentId = 1, StudentName = "Thuru", Gpa = 3.92},
6: new StudentViewModel() { StudentId = 2, StudentName = "Kristin", Gpa = 3.92},
7: new StudentViewModel() { StudentId = 3, StudentName = "Jack", Gpa = 3.92},
8: new StudentViewModel() { StudentId = 4, StudentName = "Anna", Gpa = 3.92},
9: new StudentViewModel() { StudentId = 5, StudentName = "Anderson", Gpa = 3.92},
10: new StudentViewModel() { StudentId = 6, StudentName = "Niki", Gpa = 3.92},
11: new StudentViewModel() { StudentId = 7, StudentName = "Jhon", Gpa = 3.92},
12: new StudentViewModel() { StudentId = 7, StudentName = "Brown", Gpa = 3.92},
13: };
14:
15: private static List<StudentViewModel> list2 = new List<StudentViewModel>()
16: {
17:
18: };
list0 is null, list1 and list2 are instantiated collections where list2 has no elements.
Single / SingleOrDefault
Single as the name specifies is used and should be used where there’s one and only element match the query.
1: //ArgumentNullException
2: list0.Single();
3:
4: //ArgumentNullException
5: list0.SingleOrDefault();
6:
7: // InvalidOperationException - Sequence contains no elements
8: list2.Single();
9:
10: // works return null if there's no elements matching the criteria
11: StudentViewModel st = list2.SingleOrDefault();
12:
13:
14: // InvalidOperationException - Sequence contains more than one element
15: // if collection has only one element it returns that element
16: Console.WriteLine(list1.Single());
17:
18: // works
19: Console.WriteLine(list1.Single(e => e.StudentId == 1));
20:
21: // InvalidOperationException - Sequence contains more than one matching element
22: Console.WriteLine(list1.Single(e => e.StudentId == 7));
23:
24: // InvalidOperationException - Sequence contains no matching element
25: Console.WriteLine(list1.Single(e => e.StudentId == 8));
26:
27: StudentViewModel st1;
28:
29: // InvalidOperationException - Sequence contains more than one element
30: st1 = list1.SingleOrDefault();
31:
32: //works
33: st1 = list1.SingleOrDefault(e => e.StudentId == 1);
34:
35: // InvalidOperationException - Sequence contains more than one matching element
36: st1 = list1.SingleOrDefault(e => e.StudentId == 7);
37:
38: // works st1 is null
39: st1 = list1.SingleOrDefault(e => e.StudentId == 8);
First / FirstOrDefault
First has no issues when there’re more than one element matches the query, it simply returns the first element. But if there’re no element matching the query it prompts.
1: // ArgumentNullException
2: list0.First();
3:
4: // ArgumentNullException
5: list0.FirstOrDefault();
6:
7: // InvalidOperationException - Sequence contains no elements
8: list2.First();
9:
10: // works return null if there's no elements matching the criteria
11: StudentViewModel st = list2.FirstOrDefault();
12:
13:
14: // returns the first element of the collection
15: Console.WriteLine(list1.First());
16:
17: // works
18: Console.WriteLine(list1.First(e => e.StudentId == 1));
19:
20: // returns the first element of the collection matches the query
21: Console.WriteLine(list1.First(e => e.StudentId == 7));
22:
23: // InvalidOperationException - Sequence contains no matching element
24: Console.WriteLine(list1.First(e => e.StudentId == 8));
25:
26: StudentViewModel st1;
27:
28: // returns the first element of the collection
29: st1 = list1.FirstOrDefault();
30:
31: // works
32: st1 = list1.FirstOrDefault(e => e.StudentId == 1);
33:
34: // returns the first element of the collection matches the query
35: st1 = list1.FirstOrDefault(e => e.StudentId == 7);
36:
37: // works st1 is null
38: st1 = list1.FirstOrDefault(e => e.StudentId == 8);
The code snippets and the comments describes the functions of the Single and First extension methods and their helping counter parts xxOrDefault.
About the semantic distinction.
If you know that your query should return only one element it’s better to use Single or SingleOrDefault. For example when you query a customer collection by customer ID you know that according to the business there’s only one customer ID with a specific value. First or FirstOrDefault could be used in the scenario but it doesn’t give you the semantics of the business logic.
And Single or SingleOrDefault throws an InvalidOperationException with the message ‘sequence contains more than one element’ if the collection has more than one element matching the query. This behavior sometimes can be used to validate.
Another common misuse of the above extension methods is chaining them unnecessarily with ‘where’ statement like this
1: // this is a common misuse
2: list1.Where(e => e.StudentId == 7).Single();
3:
4: list1.Single(e => e.StudentId == 7);
If you want the code for the demonstration purpose you can download it here