How it works
Several LINQ operators provide an overload whose lambda receives an element and its zero-based index. That index describes the input to that operator, not a permanent identity attached to the element. After a filter removes items, a later indexed Select sees the shorter sequence and numbers it again from zero.
The first pipeline keeps original positions zero, two, and four, then labels the filtered sequence. Its output positions are zero, one, and two. The second pipeline captures original indices in a projection before filtering, so the saved positions remain zero, two, and four when they are printed later.
This distinction is useful for validation reports. A user may need the original line number of a rejected import row, even after blank rows or comments were removed. Capture that source location at the earliest reliable boundary rather than reconstructing it from a later result index.
An index is still not a stable business identifier. Inserting an earlier row changes every later position. Use a real identifier for references that must survive edits or reordering, and use a source index for location information. Mixing those purposes can break links between diagnostics and the records they describe.
Practice by adding a filter that removes values below thirty before the indexed Where. Predict which stage's positions are considered even. Then refactor the pipeline to retain both OriginalIndex and ResultIndex, printing them beside the value so the relationship between stages is explicit.
Finally, convert the original zero-based index into a one-based line number only at the presentation boundary. Keep internal calculations consistent and test the first item, an empty sequence, and a filtered-out first item. Clear names such as SourceLine and DisplayRank prevent an innocent-looking index parameter from silently changing meaning as the query evolves.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
int[] values = { 10, 20, 30, 40, 50 };
var selected = values.Where((value, index) => index % 2 == 0);
Console.WriteLine(string.Join(",", selected.Select((value, index) => $"{index}:{value}")));
var original = values.Select((value, index) => new { value, index })
.Where(row => row.index % 2 == 0);
Console.WriteLine(string.Join(",", original.Select(row => $"{row.index}:{row.value}")));
Expected output
0:10,1:30,2:50 0:10,2:30,4:50
An indexed operator numbers the sequence that reaches that particular stage.