What Aggregate Functions Do
LINQ's aggregate operators — Count(), Sum(), Average(), Min(), and Max() — reduce an entire IEnumerable<T> sequence down to a single scalar value instead of producing a new sequence. Unlike Select() or Where(), which build up a query that is only evaluated when you enumerate it, aggregate operators trigger immediate execution the moment you call them, walking the whole source right there.
Cricket analogy: Just as a scorecard reduces an entire innings of deliveries into one number like 'total runs 287', Sum() collapses a whole sequence of order amounts into a single total the instant you call it, rather than deferring like Where() does.
Count and LongCount
Count() enumerates the source to tally its elements, but if the underlying type already implements ICollection<T> (like List<T> or an array), LINQ's implementation short-circuits and reads the Count or Length property directly for an O(1) lookup instead of iterating. Count(predicate) counts only elements matching a condition without ever materializing a filtered intermediate list, and LongCount() exists for sequences whose element count could exceed int.MaxValue, returning a long instead of an int.
Cricket analogy: A scorer checking 'how many fours were hit' flips through the ball-by-ball log and tallies only boundary events, just as Count(predicate) counts matching elements without building a separate list of just the fours.
Sum and Average
Sum() requires a selector when the source isn't already a sequence of numbers, and it has overloads for int, long, decimal, double, float, and their nullable counterparts, always returning the same type family as the input (int in, int out; ignoring nulls in nullable overloads). Average() is different: even Average() on a sequence of ints returns a double (or you can select into decimal for currency), because an average of whole numbers can be fractional, and both Sum() and Average() will throw an InvalidOperationException on Average() over an empty sequence unless you use the nullable overload, which returns null instead.
Cricket analogy: A batter's strike rate is reported as a decimal like 135.42 even though runs and balls faced are both whole numbers, just as Average() promotes an int sequence to a double result rather than truncating.
Combining Where With Aggregates
It's common to filter with Where() before aggregating, such as orders.Where(o => o.Status == "Shipped").Sum(o => o.Total), but because Where() is deferred and Sum() is immediate, each call to Sum() or Average() on the same query re-enumerates the source from scratch; if the source is an expensive IEnumerable<T> like a database query or a generator, calling multiple aggregates back-to-back (Count(), then Sum(), then Average()) can trigger the query three separate times unless you materialize with ToList() first.
Cricket analogy: Re-running a video review for every single decision — LBW, run-out, boundary — instead of watching the footage once and noting all three outcomes wastes time, just as calling Count(), Sum(), and Average() separately re-enumerates a lazy source three times instead of materializing it once with ToList().
var orders = new List<Order>
{
new Order { Id = 1, Status = "Shipped", Total = 120.50m },
new Order { Id = 2, Status = "Pending", Total = 45.00m },
new Order { Id = 3, Status = "Shipped", Total = 89.99m },
};
// Count with a predicate — no intermediate list is built
int shippedCount = orders.Count(o => o.Status == "Shipped"); // 2
// Sum requires a selector when the source isn't plain numbers
decimal shippedTotal = orders.Where(o => o.Status == "Shipped")
.Sum(o => o.Total); // 210.49
// Average always returns a floating type, and throws on empty sequences
double avgLineItems = orders.Select(o => o.Total).DefaultIfEmpty(0).Average();
// Materialize once to avoid re-enumerating three times
var shipped = orders.Where(o => o.Status == "Shipped").ToList();
var summary = new
{
Count = shipped.Count,
Total = shipped.Sum(o => o.Total),
Average = shipped.Average(o => o.Total)
};Calling Average() or Min()/Max() on an empty sequence throws an InvalidOperationException at runtime — LINQ has no sensible scalar to return. Guard with DefaultIfEmpty(), check Any() first, or use the nullable overloads (e.g., Average(x => (int?)x.Value)) which return null instead of throwing.
Count() is smart about its source: for any type implementing ICollection<T> (List<T>, arrays, HashSet<T>), calling .Count() is an O(1) property read rather than an O(n) walk — but the moment you add a predicate, e.g. Count(x => x.IsActive), it must enumerate because filtering can't be pre-computed.
- Aggregate operators (Count, Sum, Average, Min, Max) trigger immediate execution and return a single scalar.
- Count() short-circuits to an O(1) property read for ICollection<T> sources, but not when a predicate is supplied.
- LongCount() is for sequences that may exceed int.MaxValue elements.
- Sum() preserves the numeric type family of its selector; Average() always promotes to double or decimal.
- Average() and Min()/Max() throw InvalidOperationException on empty sequences unless using nullable overloads.
- Chaining multiple aggregates on a deferred query re-enumerates the source each time — materialize with ToList() first.
- Where() is deferred and lazy; the aggregate call is what actually forces enumeration.
Practice what you learned
1. What does calling orders.Count() return when orders is a List<Order>?
2. What is the return type of orders.Select(o => o.Quantity).Average() where Quantity is an int?
3. What happens when you call Average() on an empty IEnumerable<int>?
4. Why might calling Count(), Sum(), and Average() separately on the same LINQ query be inefficient?
5. Which method should you use to count only elements matching a condition, without building a separate filtered list?
Was this page helpful?
You May Also Like
First, Single, and ElementAt
Understand the differences between First, Single, and ElementAt for extracting one element from a LINQ sequence, and how their exception behavior differs.
Any, All, and Contains
Master LINQ's boolean-returning quantifier operators — Any, All, and Contains — and how their short-circuiting behavior makes them efficient existence checks.
Aggregate and Custom Accumulators
Learn how LINQ's Aggregate() operator implements a general-purpose fold, letting you build custom accumulation logic beyond Sum, Count, and Average.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics