Aggregate Data in C# LINQ: Sum, Average, Min, Max

LINQ (Language Integrated Query) in C# provides powerful aggregation operators that allow you to compute summary values from collections without writing imperative loops. This guide explains how to calculate totals with Sum, determine means with Average, find extremes with Min and Max, and perform complex custom rollups using Aggregate, accompanied by practical, clear C# code examples.


Setup: Sample Data

The examples below use this collection of objects:

public record Product(string Name, decimal Price, int UnitsSold);

var products = new List<Product>
{
    new("Laptop", 1200.00m, 5),
    new("Mouse", 25.50m, 40),
    new("Keyboard", 75.00m, 15),
    new("Monitor", 300.00m, 8)
};

Sum

The Sum method calculates the total of numeric values in a sequence. You can sum primitive numeric collections directly or pass a lambda expression to select a specific numeric property from objects.

// Direct sum of a numeric collection
int[] numbers = { 10, 20, 30, 40 };
int totalNumber = numbers.Sum(); // Result: 100

// Sum with a selector on an object property
decimal totalInventoryPrice = products.Sum(p => p.Price); // Result: 1600.50

// Calculating derived totals
decimal totalRevenue = products.Sum(p => p.Price * p.UnitsSold); // Result: 9545.00

Average

The Average method computes the arithmetic mean of a sequence of numeric values. It supports types such as int, long, float, double, and decimal.

// Average of primitive values
double[] ratings = { 4.5, 3.8, 5.0, 4.2 };
double meanRating = ratings.Average(); // Result: 4.375

// Average with an object selector
decimal averagePrice = products.Average(p => p.Price); // Result: 400.125

// When applied to integers, Average returns double
int[] scores = { 1, 2, 4 };
double averageScore = scores.Average(); // Result: 2.3333333333333335

Note: Calling Average on an empty collection throws an InvalidOperationException.


Min and Max

Min and Max retrieve the smallest and largest values in a collection, respectively. In modern .NET, LINQ also supports MinBy and MaxBy to return the entire object associated with the extreme value rather than just the numeric property.

// Finding minimum and maximum scalar values
decimal lowestPrice = products.Min(p => p.Price);   // Result: 25.50
int mostUnitsSold = products.Max(p => p.UnitsSold); // Result: 40

// MinBy and MaxBy return the actual object (available in .NET 6+)
Product? cheapestProduct = products.MinBy(p => p.Price);
// Result: Product { Name = "Mouse", Price = 25.50, UnitsSold = 40 }

Product? topSeller = products.MaxBy(p => p.UnitsSold);
// Result: Product { Name = "Mouse", Price = 25.50, UnitsSold = 40 }

Aggregate

The Aggregate method applies an accumulator function over a sequence. It is the most versatile LINQ aggregation operator, serving as the foundation for fold/reduce operations.

1. Basic Accumulator

Operates directly on the elements of the collection:

string[] words = { "LINQ", "makes", "data", "aggregation", "easy" };

string sentence = words.Aggregate((current, next) => $"{current} {next}");
// Result: "LINQ makes data aggregation easy"

2. Accumulator with a Seed Value

Allows you to supply an initial value, which can be of a different type than the collection elements:

// Seed with an initial account balance of $1000 and apply debits
decimal[] expenses = { 120.50m, 45.00m, 210.75m };
decimal startingBalance = 1000.00m;

decimal remainingBalance = expenses.Aggregate(
    startingBalance, 
    (balance, expense) => balance - expense
);
// Result: 623.75

3. Accumulator with Seed and Result Selector

Transforms the final accumulated value into an alternative output format:

// Generate a comma-separated list of product names formatted in uppercase
string csvProductNames = products.Aggregate(
    new StringBuilder(),
    (sb, product) => sb.Append(product.Name).Append(", "),
    sb => sb.ToString().TrimEnd(',', ' ').ToUpper()
);
// Result: "LAPTOP, MOUSE, KEYBOARD, MONITOR"