Understanding TryGetNonEnumeratedCount in C#
In C# 10 and .NET 6, Microsoft quietly introduced a handy little method called TryGetNonEnumeratedCount. It’s part of the LINQ extensions and is designed to help developers retrieve the count of an enumerable without forcing enumeration — improving performance and avoiding unwanted side effects.
This post explains what it does, when to use it, and how it differs from the traditional Count() LINQ method.
The Problem: Counting an Enumerable
In everyday LINQ code, we often call:
int count = myEnumerable.Count();
While this looks harmless, it can sometimes be inefficient — or even dangerous.
Why?
If
myEnumerableis a collection (like aList<T>or an array),Count()is O(1) and fast.But if it’s a lazy enumerable (like something from
Select(),Where(), or a generator),Count()must iterate through all items to calculate the total — which could mean:Expensive computation
Unnecessary database queries (e.g., with Entity Framework)
Consuming enumerators that can’t be re-used
The Solution: TryGetNonEnumeratedCount
The static method TryGetNonEnumeratedCount() was added to the System.Linq.Enumerable class to solve this problem safely.
Definition
public static bool TryGetNonEnumeratedCount<TSource>(
this IEnumerable<TSource> source,
out int count);
It attempts to retrieve the count without enumerating the source.
If it can determine the count (e.g., if the source implements ICollection<T> or IReadOnlyCollection<T>), it returns true and outputs the count.
Otherwise, it returns false and does not enumerate the sequence.
Example Usage
Here’s a simple demonstration:
IEnumerable<int> numbers = Enumerable.Range(1, 100);
// Works for collections that expose Count directly
if (numbers.TryGetNonEnumeratedCount(out int count))
{
Console.WriteLine($"Count retrieved without enumeration: {count}");
}
else
{
Console.WriteLine("Unable to get count without enumeration.");
}
Output:
Count retrieved without enumeration: 100
Now, compare that with a deferred (lazy) query:
IEnumerable<int> filtered = numbers.Where(n => n % 2 == 0);
if (filtered.TryGetNonEnumeratedCount(out int filteredCount))
{
Console.WriteLine($"Count retrieved: {filteredCount}");
}
else
{
Console.WriteLine("Count cannot be determined without enumeration.");
}
Output:
Count cannot be determined without enumeration.
How It Works Internally
The method checks whether the source implements one of the following interfaces:
ICollection<T>IReadOnlyCollection<T>ICollection(non-generic)
If any of these interfaces are implemented, the method retrieves Count directly from the collection — without iterating.
If not, it safely returns false.
In other words, it’s a non-destructive probe that doesn’t force enumeration or execution of LINQ pipelines.
Practical Scenarios
1. Preventing Accidental Enumeration
If you’re building a reusable library or middleware component, you might receive an IEnumerable<T> and want to check its size only if possible.
public void ProcessData(IEnumerable<int> source)
{
if (source.TryGetNonEnumeratedCount(out int count))
{
Console.WriteLine($"Source has {count} items (known).");
}
else
{
Console.WriteLine("Source size unknown without enumeration.");
}
// Continue processing safely...
}
This prevents accidentally consuming a one-time-use sequence.
2. Improving Diagnostics or Logging
Sometimes, you just want to log how many items are in a collection, but you don’t want to pay the cost of enumeration.
if (items.TryGetNonEnumeratedCount(out int count))
{
_logger.LogInformation($"Processing {count} items...");
}
else
{
_logger.LogInformation("Processing items (count unknown)...");
}
No side effects, no unnecessary iteration.
3. Integrating with ORM or Deferred Queries
If you use Entity Framework or another LINQ provider, calling Count() might trigger a database query.
TryGetNonEnumeratedCount avoids that unless the provider implements IReadOnlyCollection<T> — meaning it won’t send SQL just to count the rows.
Performance Comparison
Let’s benchmark the difference between Count() and TryGetNonEnumeratedCount for a list and a deferred query.
List<int> list = Enumerable.Range(1, 1_000_000).ToList();
IEnumerable<int> lazy = list.Where(n => n % 2 == 0);
if (list.TryGetNonEnumeratedCount(out int listCount))
{
Console.WriteLine($"List count: {listCount} (instant)");
}
if (!lazy.TryGetNonEnumeratedCount(out _))
{
Console.WriteLine("Lazy query count requires enumeration.");
}
Result:
listgives you an immediate result (O(1))lazycannot be counted without iterating through all items
TryGetNonEnumeratedCount is a quick, zero-allocation check before you decide whether to enumerate.
When to Use It
SituationRecommendationYou need to check count without side effects✅ Use TryGetNonEnumeratedCountYou know the sequence is a collectionUse .Count directlyYou must ensure the real number of elementsUse Count() (forces enumeration)You’re working with potentially lazy sequencesPrefer TryGetNonEnumeratedCount before Count()
Summary
TryGetNonEnumeratedCount is a small but powerful addition to the LINQ toolbox.
It lets you safely check whether a collection’s size is known — without triggering iteration, evaluation, or expensive database calls.
In short:
🔹
Count()— gives you the real count, but might enumerate.
🔹TryGetNonEnumeratedCount()— gives you the count if available, safely and instantly.
When writing performance-sensitive or reusable code, this method can help you avoid unnecessary work and subtle bugs — all with a single line of defensive programming.
Code Demo: Benchmarking TryGetNonEnumeratedCount vs. Count()
Let’s see how TryGetNonEnumeratedCount performs in real scenarios using BenchmarkDotNet, the standard performance analysis library for .NET.
Step 1: Create the Console Project
dotnet new console -n TryGetNonEnumeratedCountDemo
cd TryGetNonEnumeratedCountDemo
dotnet add package BenchmarkDotNet
Step 2: Replace Program.cs with the Following
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<CountBenchmarks>();
}
}
[MemoryDiagnoser]
public class CountBenchmarks
{
private List<int> _numbers;
private IEnumerable<int> _lazyNumbers;
[GlobalSetup]
public void Setup()
{
_numbers = Enumerable.Range(1, 1_000_000).ToList();
_lazyNumbers = _numbers.Where(n => n % 2 == 0);
}
[Benchmark(Description = "List.Count()")]
public int ListCount()
{
return _numbers.Count();
}
[Benchmark(Description = "List.TryGetNonEnumeratedCount()")]
public int ListTryGetNonEnumeratedCount()
{
if (_numbers.TryGetNonEnumeratedCount(out int count))
return count;
return -1;
}
[Benchmark(Description = "Deferred Count()")]
public int DeferredCount()
{
return _lazyNumbers.Count();
}
[Benchmark(Description = "Deferred TryGetNonEnumeratedCount()")]
public int DeferredTryGetNonEnumeratedCount()
{
if (_lazyNumbers.TryGetNonEnumeratedCount(out int count))
return count;
return -1;
}
}
Step 3: Run the Benchmark
Run it using:
dotnet run -c Release
Step 4: Example Results
A typical BenchmarkDotNet output might look like this:
| Method | Mean | Allocated |
|------------------------------------ |----------:|-----------:|
| List.Count() | 0.020 μs | 0 B |
| List.TryGetNonEnumeratedCount() | 0.018 μs | 0 B |
| Deferred Count() | 70.500 μs | 64 B |
| Deferred TryGetNonEnumeratedCount() | 0.018 μs | 0 B |
Step 5: Interpretation
List.Count()andTryGetNonEnumeratedCount()both complete instantly for aList<T>since the count is known.Deferred Count()is much slower — it must enumerate all 1,000,000 elements.Deferred TryGetNonEnumeratedCount()returns immediately, recognizing it can’t determine the count and safely skipping enumeration.
Takeaways
TryGetNonEnumeratedCountcan avoid full enumeration when the sequence type supports direct count access.For lazy or deferred LINQ pipelines, it’s a safe probe that won’t trigger expensive operations.
In real-world use, it’s most valuable in libraries, APIs, or diagnostic code that might receive unknown
IEnumerable<T>inputs.
TL;DR
CaseCount()TryGetNonEnumeratedCount()Array/List✅ Fast✅ FastLazy LINQ sequence❌ Enumerates entire sequence✅ Safe checkExpensive query (e.g., EF Core)❌ Executes query✅ No query triggered