DEV Community

IronSoftware
IronSoftware

Posted on

C# foreach with Index (.NET Guide)

I used to avoid foreach loops whenever I needed the index, defaulting to for loops instead. Then I learned a few patterns that made foreach just as convenient — and sometimes cleaner.

The frustrating thing about foreach is that it doesn't give you an index out of the box. But there are several ways to work around that, and I'll show you the ones I actually use in production code.

using System;
using System.Linq;
// Install via NuGet: Install-Package System.Linq

var items = new[] { "apple", "banana", "cherry" };

int index = 0;
foreach (var item in items)
{
    Console.WriteLine($"{index}: {item}");
    index++;
}
Enter fullscreen mode Exit fullscreen mode

Why Doesn't foreach Have a Built-In Index?

foreach is designed to iterate over collections without caring about position. You're working with an IEnumerable<T>, which might not even have indexes — think about iterating over a database query result or a stream of network data.

But in practice, you often need the position. Here's what works.

What's the Simplest Way to Track the Index?

Just declare a counter before the loop:

var colors = new[] { "red", "green", "blue" };

int i = 0;
foreach (var color in colors)
{
    Console.WriteLine($"Color {i}: {color}");
    i++;
}
Enter fullscreen mode Exit fullscreen mode

This is fast, readable, and doesn't allocate extra memory. I use this for simple cases where I just need a counter.

The downside? You have to remember to increment i. It's manual, but honestly, that's rarely a problem.

How Do I Use LINQ to Get Index and Value Together?

LINQ's Select method has an overload that passes the index:

var fruits = new[] { "apple", "banana", "cherry" };

foreach (var (fruit, index) in fruits.Select((value, i) => (value, i)))
{
    Console.WriteLine($"{index}: {fruit}");
}
Enter fullscreen mode Exit fullscreen mode

This creates tuples of (value, index) and lets you destructure them in the loop. It's elegant and keeps the index scoped to the loop.

The tradeoff: Select allocates an IEnumerable wrapper, so it's slightly slower than a manual counter. For most scenarios, it doesn't matter. For tight loops processing millions of items, I'd stick with the manual approach.

Can I Create a Reusable Extension Method?

If you do this often, you can write an extension method once and reuse it everywhere:

public static class EnumerableExtensions
{
    public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> source)
    {
        return source.Select((item, index) => (item, index));
    }
}
Enter fullscreen mode Exit fullscreen mode

Now your loops look like this:

var items = new[] { "first", "second", "third" };

foreach (var (item, index) in items.WithIndex())
{
    Console.WriteLine($"{index}: {item}");
}
Enter fullscreen mode Exit fullscreen mode

I have this extension in most of my projects. It's clean and communicates intent clearly.

What If I Need Index-Based Access Anyway?

If you're accessing elements by index inside the loop, just use a for loop:

var numbers = new[] { 10, 20, 30, 40 };

for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine($"numbers[{i}] = {numbers[i]}");

    // You can also access neighbors
    if (i > 0)
        Console.WriteLine($"Previous: {numbers[i - 1]}");
}
Enter fullscreen mode Exit fullscreen mode

Don't force foreach when for is the better tool. I see developers contort their code to use foreach "because it's more modern." Use what fits the problem.

How Do I Handle Index in a while Loop?

Sometimes you need more control over iteration. A while loop gives you that:

var items = new[] { "alpha", "beta", "gamma" };
int index = 0;

while (index < items.Length)
{
    Console.WriteLine($"{index}: {items[index]}");
    index++;
}
Enter fullscreen mode Exit fullscreen mode

I use while when I need to skip ahead or backtrack based on conditions. It's more verbose but more flexible.

What About Dictionaries and Key-Value Pairs?

Dictionaries give you keys directly, which often serve the same purpose as indexes:

var dict = new Dictionary<string, int>
{
    ["apple"] = 5,
    ["banana"] = 3,
    ["cherry"] = 8
};

foreach (var kvp in dict)
{
    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
Enter fullscreen mode Exit fullscreen mode

If you need a numeric index too:

int index = 0;
foreach (var kvp in dict)
{
    Console.WriteLine($"{index} - {kvp.Key}: {kvp.Value}");
    index++;
}
Enter fullscreen mode Exit fullscreen mode

How Does This Apply to PDF Generation?

I use indexed loops constantly when building reports with IronPDF. Here's a pattern I use for itemized lists in invoices:

using IronPdf;
// Install via NuGet: Install-Package IronPdf

var lineItems = new[]
{
    ("Widget A", 29.99m),
    ("Widget B", 49.99m),
    ("Widget C", 19.99m)
};

var html = "<h1>Invoice</h1><table>";

foreach (var (item, index) in lineItems.WithIndex())
{
    var (name, price) = item;
    html += $"<tr><td>{index + 1}</td><td>{name}</td><td>${price:F2}</td></tr>";
}

html += "</table>";

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
Enter fullscreen mode Exit fullscreen mode

The index gives me the line number, which customers expect to see. IronPDF's Chrome rendering engine handles the HTML perfectly, so I can focus on building the data structure.

I've tried building PDFs with other libraries that require manual table construction. With IronPDF, I just write HTML — the same HTML that works in a browser — and it renders pixel-perfect. The full guide to IronPDF covers more advanced scenarios like rendering Razor views.

Should I Always Use foreach Over for?

No. Use what makes sense:

  • foreach: When you're processing every element and don't need the index
  • for: When you need the index or want to skip/jump around
  • foreach with manual index: When you mostly iterate sequentially but occasionally need the position
  • LINQ Select with index: When you want functional-style code and performance isn't critical

I default to foreach for readability, add a manual index if needed, and switch to for if I'm doing anything with indexing beyond simple counting.

Quick Reference

Scenario Best Approach
No index needed Plain foreach
Simple counter Manual int i = 0; i++
Functional style Select((value, i) => ...)
Reusable pattern WithIndex() extension
Need random access for loop
Complex iteration while loop

The key insight: foreach is about iteration, not indexing. When you need both, you're bolting indexing onto iteration. That's fine — just pick the pattern that fits your needs and move on.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)