Back to Blog
C#

C# LINQ ToArray: Usage and Performance

Learn how C# LINQ ToArray materializes sequences into arrays, when to use it, and what it can cost in memory and performance.

LINQToArrayC#IEnumerablePerformance
Diagram showing an IEnumerable sequence being converted into a compact array using LINQ ToArray method.

When you call ToArray() on an IEnumerable<T> in C# LINQ, you are asking LINQ to materialize the sequence into a T[] array. This is a common operation, but its behavior and cost are often misunderstood. This article explains how ToArray works, when it is the right choice, and where it can cause unnecessary allocations.

What ToArray Actually Does

ToArray is an extension method defined in System.Linq. It takes an IEnumerable<T> and returns a T[] containing the same elements in the same order. The method forces immediate execution of any deferred query; if you have a lazy sequence, such as a LINQ query or a generator, calling ToArray enumerates it completely and stores the results.

The method is not lazy. It consumes the entire source sequence and builds an array. This behavior is useful when you need a snapshot of the data or need to pass the result to code that expects an array.

Basic Usage and Syntax

The syntax is straightforward:

IEnumerable<int> numbers = Enumerable.Range(1, 10); int[] array = numbers.ToArray();

You can also call it on the result of a LINQ query:

var filtered = items.Where(x => x.IsActive).ToArray();

The method works with any IEnumerable<T>, including arrays, lists, and custom iterators. It is a standard LINQ operator and is available in current .NET implementations.

How ToArray Works Internally

ToArray does not know the size of an arbitrary source sequence in advance. When the source is an ICollection<T>, it can query Count and allocate the exact size. Otherwise, it uses a dynamically growing buffer, starting with a small capacity and increasing it as needed. After enumeration, it returns an array whose length matches the sequence. The exact allocation pattern is an implementation detail, but the general cost is a complete enumeration plus one or more array allocations and element copies.

The internal implementation is similar to how List<T> grows, but the final step differs. ToList can reuse the buffer as the list's internal array, while ToArray generally needs to return an array of exactly the element count. This can be a small overhead, but it matters for large sequences.

When ToArray Is the Right Choice

Use ToArray when you need a fixed-size, indexable collection that you will not resize. Arrays have a slightly smaller memory footprint than List<T> because they do not carry list metadata. For sequential access, both arrays and List<T> store elements in a contiguous block, so the practical difference is usually small. If you need to pass data to an API that expects an array, ToArray is the direct conversion.

For example, if you are building a method that returns a read-only collection and you know the size will not change, an array is a clean and efficient choice. It also communicates intent: the consumer cannot accidentally add or remove elements.

ToArray vs ToList

Both materialize a sequence, but they produce different types. The table below compares typical usage:

CriterionToArrayToList
Result typeT[]List<T>
ResizabilityFixed sizeCan add/remove
Memory overheadSlightly lower (no list metadata)Higher (list metadata and possible overallocated capacity)
Common useFixed-size data, API contractsDynamic collections, frequent modifications

The choice depends on whether you need to modify the collection after creation. If you only need read access, an array is more compact. If you need to add or remove elements, a List<T> is more practical.

Performance and Memory Considerations

The main cost of ToArray is allocating a new array and copying the elements. If the source is not an ICollection<T>, the internal buffer grows, causing multiple allocations and copies. This can be wasteful for very large sequences. In contrast, ToList uses a similar growth strategy, but it can avoid a final copy because its internal buffer becomes the list's storage. However, ToList may overallocate capacity, leaving unused slots.

If you already have an array and call ToArray on it, the method returns a new array even if the source is already an array. This is a common mistake that causes an unnecessary copy. ToArray on an array returns a shallow copy, not the same reference.

For large sequences, the multiple allocations and copies can be a bottleneck. If you know the size in advance, you can avoid buffer growth by using a source that implements ICollection<T>, such as a List<T> or another array. In those cases, ToArray allocates the exact size and copies once.

Common Pitfalls and Edge Cases

  • Calling ToArray on an empty sequence returns an empty array, not null.
  • ToArray does not deep-copy the elements; it only copies references. If the elements are mutable, changes to the original objects are reflected in the array.
  • ToArray forces enumeration of the entire sequence. If the sequence is infinite or has side effects, this can cause unexpected behavior or an infinite loop.
  • If the source sequence throws during enumeration, the exception propagates and the partially built array is discarded; the partial results are not recoverable.

Alternatives to ToArray

If you know the size in advance and already have a collection, you can create the array and copy elements manually:

var list = new List<int>(); // ... fill list int[] array = new int[list.Count]; list.CopyTo(array);

This avoids ToArray's buffer-growth path, but it is more verbose and still copies all elements. If the size is not known, an arbitrary IEnumerable<T> must be enumerated before an array can be allocated. In most cases, ToArray is the simplest and clearest choice; a custom buffer strategy is rarely worth the complexity.

When Not to Use ToArray

Avoid ToArray when you only need to iterate once. Streaming with foreach or using LINQ operators like Select without materialization is more memory-efficient. Also, if you need to pass a sequence to a method that only requires IEnumerable, keep it lazy.

For example, if you are chaining multiple LINQ operations and only the final result is needed, applying ToArray early forces the entire pipeline to execute before the next operation. This can break streaming and increase memory usage. In such cases, defer materialization until the end.

If you need to return a read-only collection from a method, consider returning IReadOnlyList<T> or IReadOnlyCollection<T> instead of an array. This gives you the flexibility to change the internal representation later without breaking callers. An array is a concrete type with a fixed Length and allows element assignment, which may not be desirable.

C# LINQ ToArray: Usage, Performance, and Alternatives | RYUSLOG DEV