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 myEnumerable is a collection (like a List<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:

  • list gives you an immediate result (O(1))

  • lazy cannot 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() and TryGetNonEnumeratedCount() both complete instantly for a List<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

  • TryGetNonEnumeratedCount can 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

Next
Next

Understanding Task<T> vs. ValueTask<T> in C#