ToList and what it returns

1 min read
On trails:CSLINQstep 3/4

ToList() forces immediate execution of a deferred query and copies the results into a new List<T>, a snapshot that no longer follows the source (see deferred executionDeferred executionA LINQ query does not run when you write it. It runs when you enumerate it.Opens in a new tab).

C#
List<T> ToList<T>(IEnumerable<T> source){    var result = new List<T>();    foreach (var item in source) // this is what triggers execution        result.Add(item);    return result;}

Return type

It returns a concrete List<T>, which has indexing, Count, Add and Remove. Stored in an IEnumerable<T> variable, the same object only supports enumeration.

C#
List<int> list = numbers.Where(n => n > 2).ToList(); list[0] = 99;                  // works: List<T> supports indexinglist.Add(100);                 // works: List<T> supports addingConsole.WriteLine(list.Count); // works, and instantly IEnumerable<int> query = numbers.Where(n => n > 2).ToList(); query[0] = 99;  // ERROR: IEnumerable has no indexerquery.Add(100); // ERROR: IEnumerable has no Add method

The interfaces build up: IEnumerable<T> can be looped over, ICollection<T> adds Count, Add and Remove, IList<T> adds indexing, and List<T> is the class.

Where it matters

Return IEnumerable<T> when callers only iterate, and List<T> when they need indexing, counting or changes. Calling ToList() too early on a database query is a separate cost, covered in LINQ providersLINQ providers: IQueryable and IEnumerableWhere a LINQ query actually runs depends on its provider, and on IQueryable versus IEnumerable.Opens in a new tab.