As a JetBrains Developer Advocate, I’ve become more exposed to other languages and technology stacks. Exposure is a good thing, as it allows me to see what the .NET ecosystem does well, and notice the parts where it could improve. One of the places where C# specifically could improve is in its use of Tuples and deconstruction. One of the languages that bare a striking similarity to C# is Kotlin, a programming language created by JetBrains, and that has gained a massive following around the world.
In this post, we’ll take a look at Kotlin’s withIndex
method, and reimplementing it for C#.
Kotlin’s withIndex
First of all, for those unfamiliar with Kotlin, it is a cross-platform, statically typed, general-purpose programming language. The developers at JetBrains wanted the power of the JAVA ecosystem with more modern flourishes. You can use Kotlin to build all kinds of apps, and Android app developers have embraced it as an alternative to Java. If you want to learn more about Kotlin, you should read more at Kotlinlang.org.
Working through Google’s Codelabs Kotlin samples, I came upon this code sample.
val school = arrayOf("shark", "salmon", "minnow")
for ((index, element) in school.withIndex()) {
println("Item at $index is $element\n")
}
I can’t tell you how many times I’ve needed both the index
and the item
in a C# loop. In Kotlin, this is a first-class feature. I’ll be honest; I was a bit jealous seeing this feature.
We can do this in C#, but it will take newer C# features and some extension method magic. Let’s see how we can accomplish the functionality of withIndex
in C#.
Implementing in C#
Let’s look at the final usage of our C# WithIndex
method.
static class Program
{
static void Main()
{
var school = new[] {"shark", "salmon", "minnow"};
foreach (var (index, value) in school.WithIndex())
{
Console.WriteLine($"Item at {index} is {value}");
}
}
}
As we can see, it is very similar to Kotlin’s implementation. So what’s happening here?
The Extension Method
The first step is to write an extension method that targets IEnumerable<T>
.
public static class EnumerableExtensions
{
public static IEnumerable<IndexWithValue<T>>
WithIndex<T>(this IEnumerable<T> enumerable)
{
var index = 0;
foreach (var item in enumerable)
{
yield return new IndexWithValue<T>(index++, item);
}
}
}
We could also write the extension method using LINQ.
public static class EnumerableExtensions
{
public static IEnumerable<IndexWithValue<T>>
WithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable
.Select((i, v) => new IndexWithValue<T>(v, i));
}
}
I prefer the implementation with the yield
keyword, as it doesn’t use lambda expressions and can be more expensive at times. I haven’t tested this idea, which could be an incorrect assumption. Next, we need to define our IndexWithValue
type.
IndexWithValue Struct
I’ll admit the name of IndexWithValue
is uninspired, but what it can do makes up for my lack of imagination.
public struct IndexWithValue<T>
{
public IndexWithValue(int index, T value)
{
Index = index;
Value = value;
}
public int Index { get; }
public T Value { get; }
public void Deconstruct(out int index, out T value)
=> (index, value) = (Index, Value);
}
IndexWithValue
is a structure with a Deconstruct
method. The method allows us to return the parts of our structure in a Tuple
. The deconstruction is what facilitates the breakdown inside of our foreach
method.
var (index, value) in school.WithIndex()
Here is the complete solution for folks who want to copy and paste it into their console apps.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using static System.Console;
namespace HelloWorld
{
static class Program
{
static void Main()
{
var school = new[] {"shark", "salmon", "minnow"};
foreach (var (index, value) in school.WithIndex())
{
Console.WriteLine($"Item at {index} is {value}");
}
}
}
public static class EnumerableExtensions
{
public static IEnumerable<IndexWithValue<T>>
WithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable
.Select((i, v) => new IndexWithValue<T>(v, i));
}
}
public struct IndexWithValue<T>
{
public IndexWithValue(int index, T value)
{
Index = index;
Value = value;
}
public int Index { get; }
public T Value { get; }
public void Deconstruct(out int index, out T value)
=> (index, value) = (Index, Value);
}
}
The C# 9 Way - Single File
For folks who laugh in the face of structure, there is also a way to do all of this in a single file. As you’ll see in the example below, we can remove namespaces and implement everything in less than 15 lines of code!
using System.Linq;
using System.Collections.Generic;
var school = new [] { "shark", "salmon", "minnow" };
foreach(var (index, value) in school.WithIndex())
System.Console.WriteLine($"{index}: {value}");
public static class EnumerableExtensions
{
public static IEnumerable<(int, T)> WithIndex<T>(this IEnumerable<T> set) =>
set.Select((value, index) => (index, value));
}
Thanks to Sidney Andrews (@sidney_andres) who showed me that you could also remove the struct
entirely and lean on Tuples
entirely.
Conclusion
It’s always great to look at other languages, especially Kotlin, which seems to have inspired many of C#’s newest features. Given the tools in C# like extension methods, tuples, and deconstruction, we can implement these features ourselves.
I hope you enjoyed this post, and please leave a comment telling me what other language features you’d love to see in C#.