Cursed C# - Doing shenanigans in C#

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

Resources

  • The code to this blog post can be found here.
  • The majority of code samples can be found here.
8
An error has occurred. This application may no longer respond until reloaded. Reload x