When to use IEnumerable vs List vs IQueryable

In this example, we’ll be exploring the differences between the List, IEnumerable and IQueryable types in C# and when we should use each one. All of these types can be used as a container for records. But which one should we use, and why? In this article, we will explore what each type is used for. We will also perform some example LINQ queries on each whilst timing them.

List

  • Derives from the System.Collections.Generic namespace
  • Not read-only, enumeration can be mutated so can add and remove records
  • Executed immediately and creates a new list with the contents of the enumeration in memory
  • Can iterate over all elements using a foreach loop
  • Implements IList so can access an element in a specific position/index

Best for:

  • Best for querying in-memory collections such as a list or an array
  • If adding or removing records

IEnumerable

  • Derives from the System.Collections.Generic namespace
  • Read-only, enumeration can not be mutated
  • Deferred execution, meaning that a query will not get executed until iterating
  • Does not support lazy loading
  • When querying data from a database, IEnumerable executes the select query on the server, loads the data in-memory on the client and then filters the data

Best for:

  • Best for querying in-memory collections such as a list or an array
  • When reading or iterating over data only

IQueryable

  • System.Linq
  • Supports lazy loading
  • When querying from a database, IQueryable executes the select query on the server including all filters
  • Intended use is as a querying language and to provide expressions to be translated into the desired format i.e. an ORM such as LINQ to SQL, or Entity Framework. In other words, the code doesn’t get executed but is translated into the format required at its destination

Best for:

  • Querying data from out-memory collections such as a remote database or service

Performance of List vs IEnumerable vs IQueryable

In the following examples, we will query a set of in-memory data to determine if the points made above are reflected in practice. First, we need a C# class that will provide us with adequate test data. I am using a package called Bogus to generate a list of Person records. Bogus can be found here.

internal class MockDataRepository
    {
        private readonly List<Person> people;
        public MockDataRepository()
        {
            people = new();
            for (int i = 0; i < 1000; i++)
            {
                people.Add(new Person());
            }
        }
        public List<Person> PeopleAsList() => people;
        public IEnumerable<Person> PeopleAsEnumerable() => people.AsEnumerable();
        public IQueryable<Person> PeopleAsQueryable() => people.AsQueryable();
    }

With our mock data, we can then perform various LINQ queries and record the time taken using the stopwatch class.

Count query

MockDataRepository mockDataRepository = new();
Stopwatch timer;
TimeSpan timeTaken;
var peopleList = mockDataRepository.PeopleAsList();
var peopleEnumerable = mockDataRepository.PeopleAsEnumerable();
var peopleQueryable = mockDataRepository.PeopleAsQueryable();
timer = new Stopwatch();
timer.Start();
var peopleListCount = peopleList.Count();
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("List count: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleEnumerableCount = peopleEnumerable.Count();
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Enumerable count: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleQueryableCount = peopleQueryable.Count();
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Queryable count: " + timeTaken.TotalMilliseconds);
List count0.0519
IEnumerable count0.0098
IQueryable count49.5448
Time taken in milliseconds to perform a count on a List, IEnumerable and IQueryable

DateTime query

timer = new Stopwatch();
timer.Start();
var peopleListDateTimeQuery = peopleList.Where(p => p.DateOfBirth < DateTime.Now.AddYears(-18));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("List datetime query: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleEnumerableDateTimeQuery = peopleEnumerable.Where(p => p.DateOfBirth < DateTime.Now.AddYears(-18));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Enumerable datetime query: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleQueryableDateTimeQuery = peopleQueryable.Where(p => p.DateOfBirth < DateTime.Now.AddYears(-18));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Queryable datetime query: " + timeTaken.TotalMilliseconds);
List DateTime query0.3004
IEnumerable DateTime query0.0016
IQueryable DateTime query 2.9178
Time taken in milliseconds to perform a DateTime query on a List, IEnumerable and IQueryable

String Contains query

timer = new Stopwatch();
timer.Start();
var peopleListContainsQuery = peopleList.Where(p => p.Email.Contains("hotmail"));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("List contains query: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleEnumerableContainsQuery = peopleEnumerable.Where(p => p.Email.Contains("hotmail"));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Enumerable contains query: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleQueryableContainsQuery = peopleQueryable.Where(p => p.Email.Contains("hotmail"));
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Queryable contains query: " + timeTaken.TotalMilliseconds);
List String Contains query0.0069
IEnumerable String Contains query0.0055
IQueryable String Contains query0.1933
Time taken in milliseconds to perform a String Contains query on a List, IEnumerable and IQueryable

Order By query

timer = new Stopwatch();
timer.Start();
var peopleListOrderBy = peopleList.OrderBy(p => p.LastName);
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("List order by: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleEnumerableOrderBY = peopleEnumerable.OrderBy(p => p.LastName);
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Enumerable order by: " + timeTaken.TotalMilliseconds);
timer = new Stopwatch();
timer.Start();
var peopleQueryableOrderBy = peopleQueryable.OrderBy(p => p.LastName);
timer.Stop();
timeTaken = timer.Elapsed;
Console.WriteLine("Queryable order by: " + timeTaken.TotalMilliseconds);
List Order By query0.3117
IEnumerable Order By query0.0078
IQueryable Order By query0.2609
Time taken in milliseconds to perform an Order By query on a List, IEnumerable and IQueryable

Conclusion

It can be seen in the results of each query that IQueryable is not as efficient as the other types as it takes the longest for most of the LINQ queries performed. IEnumerable the fastest, as was expected for a read-only in-memory data collection, with List performing far better than IQueryable in most scenarios.

In the future, I hope to repeat the same tests but with data deriving from a database to reflect a website fetching and manipulating data.

Scroll to Top