Collections & LINQ · Lesson 9

Empty sequences

A filter that keeps nothing has a sum of 0.

An empty result is a real sequence state

A filter can validly select no elements. That does not mean the program failed, the source was missing, or the runtime forgot to execute the query. It means every observed element failed the selection rule. Once that happens, the terminal operation's empty-sequence contract determines the result. Different aggregates answer different questions and therefore need not handle emptiness in the same way.

The example uses the nonnullable decimal Sum overload. Its additive identity is zero, so an empty decimal sequence sums to zero. Count of that same sequence is also zero, but for a different reason: there are no selected observations. Average and First do not have the same ordinary nonnullable empty-input result. Learn the contract of the operation you actually call instead of generalizing from one convenient example to all of LINQ.

Explain why the source is nonempty and the selection is empty

The original array contains one, two, and three as decimal values. The predicate requires a price of at least ten. One fails, two fails, and three fails, leaving no elements for Sum to add. Sum returns a decimal zero, and WriteLine prints 0. This visible zero is the calculated answer. It is not an output placeholder, a default screen value, or evidence that the source array contained zero-valued elements.

The source array still contains its three original values after the query. Where does not remove them or rewrite them. The query enumerates the source when Sum requests a result, evaluates the predicate for each value, and reaches the end without an accepted value. If you instrument the selection during an experiment, you can observe those checks even though the final sequence is empty. Keep that diagnostic instrumentation separate from the original sample, whose only output is the final sum.

var prices = new[] { 1m, 2m, 3m };
Console.WriteLine(prices.Where(p => p >= 10m).Sum());

Zero can hide two different business situations

A total of zero can come from no qualifying observations or from qualifying observations whose values sum to zero. Those situations may need different messages in a report. Sum alone cannot distinguish them; retain a count or another explicit presence indicator when the distinction matters. Avoid replacing zero with an error automatically, because the mathematical operation is succeeding according to its contract. Decide what absence means in the domain and preserve enough information to express that policy.

Create one accepted value, then compare empty contracts

First replace the final three with 12m. Predict twelve as the only qualifying contribution, then change it to exactly 10m and predict ten because the threshold is inclusive. Restore the original data and print both selected Count and Sum. Hint: the expected pair is zero observations and zero total. This separates the reason no contributions exist from the numerical identity used by the aggregate.

Next replace Sum with First as a deliberate contract experiment on the original empty selection. Explain why the call throws instead of returning decimal zero. Then design a report that prints no matching prices when the selection is empty and prints its total otherwise. Hint: materialize once if you need a stable selected snapshot for both the presence test and the sum. For a separate signed-adjustment report with no positive-price filter, use 5m and -5m to obtain Count two and Sum zero. Keep that experiment distinct from this price query, whose threshold cannot accept negative values.

Choose an explicit empty-input policy

Before using an aggregate in a reusable calculation, identify what it does for an empty sequence and whether that behavior matches the application's requirement. For this lesson the source is nonempty, the selected sequence is empty, and Sum succeeds with zero. A useful explanation keeps all three facts separate. When presence matters beyond the total, carry that presence information through the calculation instead of trying to reconstruct it from the final number.

  • All original prices are below the inclusive threshold ten.
  • The unchanged decimal Sum prints 0 successfully.
  • An empty selection is different from an absent source object.
  • First, Average, Count, and Sum have distinct empty contracts.

A working example

var prices = new[] { 1m, 2m, 3m };
Console.WriteLine(prices.Where(p => p >= 10m).Sum());

Expected output

0
Try this
Try the filter on a smaller list.

Further reading