In this short blog post I want to show you two silly things so that you can apply right now! Both of them equally silly, but that is not the point (is it ever?).
We will see how to await an integer or TimeSpan
and how to foreach through an integer. All of this thanks to the magic of extensions methods.
foreach
everything
Let's start with a pretty useless thing: We want to foreach through an integer so that we get all the digits from the highest to lowest in the following fashion:
foreach (var i in 12)
{
Console.WriteLine(i);
}
Now sure, if we just throw this against the compiler he will complain about that non-sense. So our task is it to make it meaningful to the compiler and we can easily do that with extension methods. You see, foreach
is a bit special. We often think the thing we want to enumerate through has to be of type IEnumerable
or IEnumerator
but that isn't true. The compiler does duck-typing in foreach
's and only the methods of IEnumerator
have to be present, but we don't have to implement any interface. I covered this a bit more in detail here: Shorts: foreach on type without IEnumerable. So the only thing we have to do is to extend the int
type to have those two methods:
public static class Extensions
{
public static IntEnumerator GetEnumerator(this int i) => new(i);
}
public struct IntEnumerator
{
private readonly List<int> _listOfInts;
private int _index = -1;
public IntEnumerator(int num)
{
_listOfInts = new List<int>();
while(num > 0)
{
_listOfInts.Add(num % 10);
num /= 10;
}
_listOfInts.Reverse();
}
public int Current => _listOfInts[_index];
public bool MoveNext()
{
_index++;
return _listOfInts.Count > _index;
}
}
What we are doing here is simple: We "equip" int
with a GetEnumerator
function, which returns an IntEnumerator
. That IntEnumerator
fulfils the requirement for a foreach
loop. My enumerator does nothing more than putting the single digits of the integer into a list and returns them one by one if asked for.
I am fair, the usage of that is very very limited and there is a high chance you get fired or will be sent to a psychologist if you use this. That's why we introduce another cool method:
await
everything
Are you sick of those await Task.Delay(10);
methods? Soooo much text and still the unit is unclear. So I propose that:
// Wait 2 seconds via TimeSpan
await TimeSpan.FromSeconds(2);
Yes you saw right, we want to await a TimeSpan
object. And it is trivial to do so. Again we leverage extension methods. The same we had for foreach
applies to await
. It is pattern-based. So as long as we have a TaskAwaiter
for a specific type, we can await that. There is also a nice Microsoft article, which goes a bit deeper.
Anyway we only have to write one little extension method to make that whole construct work:
public static class Extensions
{
public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
{
return Task.Delay(timeSpan).GetAwaiter();
}
}
Yes that is all! We have an implicit conversion from TimeSpan
to TaskAwaiter
, which enables us to await a TimeSpan
. But why stop here? We can make it fancy like this:
await 2.Seconds();
Isn't that nice and verbose? Well the only thing we have to do here is to have an extension from int
to TimeSpan
and this works thanks to the groundwork we did earlier:
public static class Extensions
{
public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
{
return Task.Delay(timeSpan).GetAwaiter();
}
public static TimeSpan Seconds(this int i) => TimeSpan.FromSeconds(i);
}
So our int
becomes implicit a TimeSpan
which thanks to pattern-matching can be awaited, because the compiler sees a TaskAwaiter
. This is at least read-able and I could see some usage of that.
Conclusion
There you have it. Two things every production code is missing!Sort of