C# List Count: Count Property vs Count() Method
c# list count: Learn how to get the element count of a List<T> in C# using the Count property and the LINQ Count() method, including performance and compatibility diff...
When you need to know how many elements a List<T> holds in C#, the immediate answer is the Count property. But the LINQ extension method Count() also exists, and the choice between them affects behavior, performance, and code maintainability. The c# list count question appears constantly in code reviews because the two approaches look similar but are not interchangeable in every context.
The Count Property on List<T>
List<string> names = new List<string> { "Ada", "Grace", "Linus" }; int total = names.Count; Console.WriteLine(total); // 3
The Count property is defined directly on List<T> and returns the number of elements currently stored. It is an O(1) operation because List<T> maintains an internal counter that is updated on every Add, Insert, Remove, and Clear call. Reading Count never iterates the collection, so it is safe to call even when the list contains millions of elements.
The property returns an int. If the list holds more than int.MaxValue elements, which is practically impossible for an in-memory list, the value would overflow. For all realistic scenarios, Count is the correct and cheapest way to obtain the element count.
The Count() LINQ Extension Method
using System.Linq; List<string> names = new List<string> { "Ada", "Grace", "Linus" }; int total = names.Count();
Count() is an extension method defined in System.Linq and works on any IEnumerable<T>. When the source implements ICollection<T> or ICollection, the LINQ implementation detects that interface and reads the Count property directly instead of enumerating. For a List<T>, Count() therefore also runs in O(1) time and performs no enumeration.
The method also has an overload that accepts a predicate:
int adults = people.Count(p => p.Age >= 18);
This overload always enumerates the sequence and counts only the elements that satisfy the predicate. There is no property-based shortcut for this variant because the predicate must be evaluated against every element.
Count Property vs Count() Method
For a List<T>, both approaches resolve to the same internal counter, so the practical difference is not raw speed. The difference is in what each one communicates and where each one works.
| Aspect | Count property | Count() method |
|---|---|---|
| Available on | List<T>, arrays, types implementing ICollection<T> | Any IEnumerable<T> |
Time complexity for List<T> | O(1) | O(1) |
| Time complexity for lazy sequences | Not available | O(n) |
Requires using System.Linq | No | Yes |
| Null source exception | NullReferenceException | ArgumentNullException |
| Predicate filtering | Not supported | Supported via overload |
The table shows the key distinction: the property is a direct member of the collection type, while the method is a general-purpose extension that works on any enumerable. When the compile-time type is List<T>, the property is the more direct expression of intent.
When Count() Is the Right Choice
Count() becomes necessary when the type is IEnumerable<T> rather than List<T>. A method parameter typed as IEnumerable<T> has no Count property, so Count() is the only option without changing the signature.
public int CountItems(IEnumerable<string> items) { return items.Count(); }
This works correctly when the caller passes a List<T>, an array, or any other enumerable. The tradeoff is that the caller controls the actual runtime type. If the caller passes a lazy sequence such as a LINQ query or a yield return iterator, Count() forces full enumeration. That is O(n) and can be expensive if the sequence is large or if generating each element involves significant work.
A common pattern is to check whether the source is a collection before calling Count():
public int CountItems(IEnumerable<string> items) { if (items is ICollection<string> collection) { return collection.Count; } return items.Count(); }
This avoids full enumeration when the source is a collection while still handling lazy sequences correctly. The LINQ Count() method performs this same interface check internally, so this pattern is only necessary when you want to avoid the method call entirely or when you need the count without relying on LINQ.
Performance and Allocation Behavior
For a List<T>, both Count and Count() read the same internal field. No allocation occurs in either case, and neither approach enumerates the list. The performance consideration that matters is the lazy-sequence case.
Calling Count() on an iterator that generates values on the fly executes the entire generator. If the generator performs I/O, computes expensive values, or produces a very large sequence, the cost is proportional to the number of elements. This is the same cost as a foreach loop that increments a counter, so Count() does not add overhead beyond the enumeration itself.
A related decision is checking whether a list is empty. list.Count > 0 reads the internal counter directly. The LINQ Any() method stops at the first element, which is faster than Count() on a lazy sequence but slightly more work than reading a property on a List<T>. For a List<T>, Count > 0 is the cheapest emptiness check.
Common Mistakes and Edge Cases
A frequent mistake is calling Count() on a null reference. The Count property throws NullReferenceException because it is an instance member access. The Count() method throws ArgumentNullException because the extension method validates its this parameter. Both fail, but the exception type differs, which matters when a catch block distinguishes between the two.
Another edge case is a List<T> that has been cleared. After Clear(), Count returns 0, but Capacity remains unchanged. The internal array is not released, so memory is not immediately freed, but the element count is correctly reported as zero. This is relevant when a list is reused across requests and the developer expects memory to be reclaimed after Clear().
A third edge case involves the Count() predicate overload. If the predicate throws for a particular element, the exception propagates immediately and the count is never returned. There is no partial result. This is expected behavior but can surprise developers who assume the method handles exceptions internally.
Compatibility and Maintainability Considerations
Choosing Count over Count() keeps code working without a using System.Linq directive. Projects that deliberately avoid LINQ for consistency or to reduce the surface area of the codebase benefit from using the property directly. The property is also more discoverable: it appears in IntelliSense as a member of List<T>, while Count() requires the LINQ namespace to be imported.
Conversely, Count() works on any IEnumerable<T>, which keeps method signatures flexible. A method that accepts IEnumerable<T> and calls Count() can receive a List<T>, an array, a HashSet<T>, or a lazy sequence without changing its implementation. The cost is that the caller must understand the enumeration behavior when passing a lazy sequence.
The maintainability tradeoff comes down to the type contract. If a method genuinely needs a random-access, resizable collection, accepting List<T> and using the Count property is the clearest contract. If the method only needs to read a sequence once, accepting IEnumerable<T> and using Count() is more flexible. The choice should reflect the actual requirement rather than convenience, because changing the parameter type later is a breaking change for callers.