Like in the previous article of this series I want to compare Linq with a classical loop. This time we want to look at the common use case to check whether an item exists. Again we want to use validated clean data as input and raw data including null pointers.
Use case one: clean data
Let’s start with the first use case. We have a simple data class for a person. Out of a list of persons we want to check whether a specific person exists.
The data class is defined as following:
public class Person { public string Name { get; set; } public uint Age { get; set; } }
Our demo console application creates a list of data and calls the data query method, first with an existing person and second with a not existing one.
List<Person> persons = new List<Person>(); persons.Add(new Person() { Name = "John Doe", Age = 35 }); persons.Add(new Person() { Name = "Jane Doe", Age = 41 }); //--------- bool exist; //search existing person exist = DoesExist(persons, "Jane Doe"); Console.WriteLine("Exist: " + exist.ToString()); //search not existing person exist = DoesExist(persons, "???"); Console.WriteLine("Exist: " + exist.ToString()); Console.ReadKey();
The data query method shall be implemented twice: by using a loop and by using Linq. We start with the classical loop:
static private bool DoesExist(List<Person> persons, string name) { foreach (Person person in persons) { if (string.Equals(person.Name, name, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; }
And we implement the same function by using Linq.
static private bool DoesExist(List<Person> persons, string name) { return persons.Exists(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); }
Code Review for use case one
Both methods will survive a code review without any issues. The loop is clean and well structured. The Linq query contains a long predicate inside the Exists call. But I think as this string comparison is easy to understand it is fine to write the whole statement in this one line of code.
In this use case I prefer the Linq method because I think it is something easier and quicker to read and understand.
Use case two: dirty data
The second use case adds an important need: the query must be robust. So the data may for example contain null values. Like in the first use case the method shall return whether the person exists. Null pointers will be rated as not existing person and therefore they shall be ignored and shall not throw an exception.
In our test console application we create some dirty data. And we add additional tests to call the function with the data or even with null parameters.
List<Person> persons = new List<Person>(); persons.Add(new Person() { Name = "John Doe", Age = 35 }); persons.Add(null); persons.Add(new Person() { Name = null, Age = 38 }); persons.Add(new Person() { Name = "Jane Doe", Age = 41 }); //--------- bool exist; //search existing person exist = DoesExist(persons, "Jane Doe"); Console.WriteLine("Exist: " + exist.ToString()); //search not existing person exist = DoesExist(persons, "???"); Console.WriteLine("Exist: " + exist.ToString()); //search in a list which is not yet initialized exist = DoesExist(null, "???"); Console.WriteLine("Exist: " + exist.ToString()); Console.ReadKey();
The implemented query using the loop must be adapted to handle these special cases. The following source code shows an according implementation. The list and the list content will be checked for null values.
static private bool DoesExist(List<Person> persons, string name) { if (persons == null) { return false; } foreach (Person person in persons) { if (person == null) { continue; } if (string.Equals(person.Name, name, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; }
The implementation of the Linq query must be adapted too. A check of the whole list, as well of the single element is added.
static private bool DoesExist(List<Person> persons, string name) { if (persons == null) { return false; } Predicate<Person> personFinder = (Person p) => { if (p == null) { return false; } return string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase); }; return persons.Exists(personFinder); }
Code Review for use case two
I think both implementations are fine. They are self-explaining and easy to understand. This time I don’t see any advantage using Linq. The needed predicate definition and the null pointer checks will lead to an implementation which is nearly equal to the loop. Therefore in my opinion the two solutions are coequal.
Very creativee post