Copied to Clipboard
Scenario 4: Processing Large Datasets
// For LINQ queries, ToList vs ToArray makes minimal difference
var allRecords = dbContext.Records.ToList(); // 500K records
// vs
var allRecords = dbContext.Records.ToArray(); // 500K records
// Memory difference: < 0.01% (essentially identical)
// The real optimization: don't materialize at all if possible
await foreach (var record in dbContext.Records.AsAsyncEnumerable())
{
ProcessRecord(record); // Processes one at a time, minimal memory
}
Common Mistakes
Mistake 1: Using ToList() for Immutable Return Values
// BAD: Caller can modify the returned list
public List<string> GetApprovedStatuses()
{
return new[] { "Approved", "Verified", "Published" }.ToList();
}
// Caller can do this:
var statuses = GetApprovedStatuses();
statuses.Add("Hacked"); // Modifies "immutable" constant!
// GOOD: Return array to prevent modification
public string[] GetApprovedStatuses()
{
return new[] { "Approved", "Verified", "Published" };
}
Mistake 2: ToArray() with Unknown Size Source
// SLOWER: ToArray with unknown size enumerates twice
public int[] ProcessResults(IEnumerable<int> source)
{
// First enumeration: Count items
// Second enumeration: Fill array
return source.ToArray();
}
// If you know approximate size, ToList with capacity is better
public List<int> ProcessResults(IEnumerable<int> source, int estimatedSize)
{
var result = new List<int>(estimatedSize); // Pre-allocate
result.AddRange(source);
return result;
}
Performance Tips
Tip 1: Pre-Allocate List Capacity When Known
// SLOW: List resizes multiple times
var results = new List<Document>();
foreach (var item in items)
{
results.Add(ProcessItem(item));
}
// FAST: Pre-allocate capacity
var results = new List<Document>(items.Count);
foreach (var item in items)
{
results.Add(ProcessItem(item));
}
With known capacity, ToList() can match ToArray() performance.
Tip 2: Pre-Allocate for Incremental Building
For incremental building over 10K items, the difference is dramatic:
// BAD: Incremental building without pre-allocation
var list = new List<Document>();
foreach (var item in items) // 100K items
{
list.Add(item); // Wastes 162% more memory!
}
// GOOD: Pre-allocate capacity
var list = new List<Document>(100000);
foreach (var item in items)
{
list.Add(item); // No waste!
}
Tip 3: Return Arrays from Public APIs
// GOOD: Immutable return type
public DocumentDto[] GetRecentDocuments() => /* ... */
// Prevents:
var docs = GetRecentDocuments();
docs[0] = null; // Can still modify elements
docs = docs.Concat(newDoc).ToArray(); // But can't add/remove
When Performance Doesn’t Matter
For LINQ queries, the performance difference is negligible regardless of collection size:
// Both perform identically for LINQ queries
var userIds = query.ToList(); // 3.96 KB, 164.5 ns
var userIds = query.ToArray(); // 3.93 KB, 168.2 ns
// Difference: < 1% - won't impact your app
Choose based on intent, not performance:
- Use ToList() if you might modify the collection
- Use ToArray() for immutable return values
- Don’t worry about the performance difference for LINQ queries!
Summary
ToList():
- ✅ When you need to add/remove items
- ✅ When flexibility matters more than immutability
- ✅ Performance identical to ToArray() for LINQ queries (< 1%)
- ✅ Familiar API with many helper methods
- ❌ For incremental building without pre-allocation (wastes 162% more memory!)
ToArray():
- ✅ For immutable return values (signals intent)
- ✅ For incremental building scenarios (2.4x faster than List without pre-allocation)
- ✅ Clear intent: "This collection won’t change"
- ✅ Performance identical to ToList() for LINQ queries (< 1%)
- ❌ When you need to modify collection after creation
Pre-allocated List:
- ✅ When you know the size and need mutability
- ✅ Matches Array memory, allows modification
- ✅ Best of both worlds: new List(capacity)
Conclusion
The choice between ToList() and ToArray() isn’t about micro-optimization — it’s about context and intent.
The surprising truth from benchmarks:
-
For LINQ queries: Performance difference < 1% → Choose based on mutability needs
-
For incremental building: List without pre-allocation wastes 162% more memory → Use Array or pre-allocate
Practical decision:
Calling **.ToList() or ***.ToArray() on a LINQ query?* → Choose based on whether you need mutability (performance is identical)
Building incrementally with **.Add()?** → Pre-allocate capacity or use arrays
Need immutable return values? → Use ToArray() to signal intent
Start by choosing based on mutability and intent, not performance myths. The real performance difference only matters when building collections incrementally without pre-allocation.
Further Reading
Previous articles in this series:
What’s your experience with ToList() vs ToArray()? Have you encountered scenarios where the choice made a significant difference? Share in the comments!
CSharp #LINQ #DotNet #Performance #SoftwareEngineering