C# LINQ Sum: Syntax, Edge Cases, and Performance
c# linq sum: Learn how to use the LINQ Sum method in C# to total numeric sequences, handle empty and nullable collections, and choose between Sum and Aggregate.
c# linq sum requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The Sum method in LINQ is the standard way to total a sequence of numeric values in C#. It is part of the Enumerable class and works with any IEnumerable<T> that implements the numeric types. This article covers the syntax, the behavior with empty and nullable sequences, and the performance tradeoffs you should consider when summing large collections.
The Basic Syntax of Sum in LINQ
The simplest form of Sum operates on a sequence of numeric values, such as int, double, decimal, or their nullable counterparts. The method is an extension method defined in the System.Linq namespace, so you need using System.Linq; in your file.
using System; using System.Linq; int[] numbers = { 1, 2, 3, 4, 5 }; int total = numbers.Sum(); Console.WriteLine(total); // Output: 15
This overload returns 0 for an empty sequence. That behavior is consistent across all numeric types: if the source sequence contains no elements, Sum returns the default value for that type, which is zero. This is a deliberate design choice to avoid exceptions and matches the expectation that an empty set has a sum of zero.
The same pattern works for List<int>, IEnumerable<double>, or any other numeric collection. For floating-point types, the result is a double; for decimal, it is a decimal. The method uses the checked context by default, meaning an overflow throws an OverflowException. This is important to remember when summing large values that might exceed the type's maximum.
Summing a Projected Property with a Selector
Often you have a sequence of objects and need to sum a specific numeric property. The Sum method has an overload that accepts a selector function. The selector transforms each element into a numeric value, which is then summed.
public class Order { public int Id { get; set; } public decimal Amount { get; set; } } List<Order> orders = new List<Order> { new Order { Id = 1, Amount = 10.5m }, new Order { Id = 2, Amount = 20.75m }, new Order { Id = 3, Amount = 5.25m } }; decimal totalAmount = orders.Sum(o => o.Amount); Console.WriteLine(totalAmount); // Output: 36.50
The selector is a Func<TSource, TResult> where TResult must be one of the numeric types. This overload is convenient when you need to sum a property without first projecting the entire sequence with Select. It avoids an extra allocation and keeps the code concise.
If the property is nullable, the selector returns a nullable numeric type, and the behavior changes as described in the next section.
How Sum Handles Empty Sequences and Nullable Types
For non-nullable numeric types, an empty sequence returns 0. For nullable numeric types, the behavior is different: Sum returns null when the sequence is empty or when all elements are null. This is because the return type of Sum for a nullable sequence is the same nullable type, and the sum of an empty set is considered undefined.
int?[] nullableNumbers = { 1, null, 3 }; int? nullableSum = nullableNumbers.Sum(); Console.WriteLine(nullableSum); // Output: 4 int?[] allNull = { null, null }; int? emptySum = allNull.Sum(); Console.WriteLine(emptySum == null); // Output: True
When the sequence contains a mix of nullable values, Sum ignores null elements and sums the non-null values. This is a practical behavior that avoids manual filtering. However, it can be a source of subtle bugs if you expect null to propagate through the calculation. If you need to treat null as zero, you can use the null-coalescing operator in the selector:
int? total = orders.Sum(o => o.Amount ?? 0);
This forces the result to be 0 for an empty sequence and treats missing values as zero.
Performance Considerations for Large Collections
The Sum method is implemented as a simple loop that adds each element to an accumulator. It does not use parallelization by default. For most collections, this is efficient enough. However, there are a few performance-related points to consider.
First, Sum always iterates the entire sequence. If you only need a partial sum, you must filter the sequence first with Where or use Take, which adds an extra iteration. In such cases, a manual loop might be slightly faster because it can combine the filtering and summing in one pass, but the difference is usually negligible unless the collection is very large and the predicate is expensive.
Second, for floating-point types, the order of addition can affect the result due to rounding errors. Sum processes elements in source order. If you have a large collection of double values with vastly different magnitudes, the accumulated error might become noticeable. For financial calculations, use decimal instead of double to avoid binary floating-point precision issues.
Third, Sum uses a checked context for integral types, which means an overflow throws an exception. If you expect values that could overflow, you have two options: use a larger type like long or decimal, or use Aggregate with an unchecked context. The latter is rarely necessary, but it is worth knowing.
When to Use Aggregate Instead of Sum
The Aggregate method is a more general version of Sum. It applies an accumulator function to each element and returns the final result. You can use Aggregate to sum numbers, but it also allows custom accumulation logic, such as concatenating strings or building a custom aggregate object.
int[] numbers = { 1, 2, 3, 4 }; int total = numbers.Aggregate((acc, x) => acc + x);
This works, but it has a subtle difference: Aggregate without a seed throws an InvalidOperationException on an empty sequence, whereas Sum returns 0. If you need the empty-sequence behavior, you must provide a seed value:
int total = numbers.Aggregate(0, (acc, x) => acc + x);
For simple summation, Sum is clearer and more efficient because it is a specialized implementation. Aggregate is the right choice when you need to combine elements in a way that Sum does not support, such as calculating a product, building a comma-separated string, or performing a running calculation that depends on previous state.
| Operation | Sum | Aggregate |
|---|---|---|
| Empty sequence | Returns 0 | Throws without seed; returns seed if provided |
| Nullable elements | Ignores nulls | Depends on accumulator |
| Overflow | Throws in checked context | Depends on accumulator implementation |
| Readability | High for summation | Lower for simple summation |
Common Mistakes and Pitfalls with Sum
One common mistake is assuming that Sum works with non-numeric types, such as string. It does not; the compiler will reject the call because there is no overload for string. If you need to concatenate strings, use string.Join or Aggregate with a StringBuilder.
Another pitfall is using Sum on a sequence of long values and expecting a long result. The overload for long returns a long, but if the sum exceeds long.MaxValue, it throws. In contrast, summing int values returns an int, not a long. If you need a long result from an int sequence, you must cast the elements first:
long total = numbers.Sum(x => (long)x);
This avoids overflow for large integer collections. Similarly, summing decimal values returns a decimal, and summing float values returns a float. The return type always matches the input type, which is a design decision that can surprise developers who expect a wider type.
Finally, remember that Sum is an extension method, so it only works if the source implements IEnumerable<T>. It will not work on IQueryable<T> unless you are using a LINQ provider like Entity Framework, which translates the Sum call into a SQL aggregate. In that case, the behavior is delegated to the database, and the return type might differ based on the provider's mapping.