C# 8 : Asynchronous Streams and Static Local Functions

Asynchronous Stream
How often have you come across situations when you would want to return streams of data from an asynchronous method ? Plenty of times I would guess. The asynchronous methods were pretty limited when return data in streams, but not any longer. 

Consider the following code.

// Will not compile
public static async Task<IEnumerable<int>> Generate(int max)
{
    for (int i = 0; i < max; i++)
    {
        await Task.Delay(1000);
        yield return i;
    }
}

The above code would not compile. There is no way you could `yield return` values (or in other words, stream values) from an asynchronous method. This would have been a highly useful situation when you want to iterate over results from a query over an extremely large database. There are other countless situation the ability to stream data from asynchronous method could be useful. However, prior to C# 8.0, we were severely handicapped in such a situation.

With C# 8.0, .Net comprises a new Type called the IAsyncEnumerable, which allows us to accomplis this. The above code could be now rewritten as.

public static async IAsyncEnumerable<int> Generate(int max)
{
    for (int i = 0; i < max; i++)
    {
        await Task.Delay(1000);
        yield return i;
    }
}

//client code
await foreach (var item in Generate(10))
{
    Console.WriteLine(item);
};

The placement of `await` keyword in the consumer code needs to be noted here.

Local Static Functions
In previous versions of C#, local methods could capture the enclosing scope. For example, consider the following code.

public void Foo()
{
    int internalValue = 20;

    void Bar()
    {
        internalValue++;
    }

    Bar();
}

This enable usage of variables such as internalValue within the local method. If the usage is accidental, this might lead to huge consequences. One could not declare a static local method in the earlier versions of .Net.

With C# 8.0, .Net removed this limitation. This enables the developers to create *pure local functions* as it does not allow usage of variables from enclosing types within it.

public void Foo()
{
    int internalValue = 20;

    static void Bar()
    {
        Console.WriteLine("Do Something");
        // internalValue++; // This would throw compile error
    }

    Bar();

}

This also removes certain overhead created when using the captured variables. For example, the previous code (C# < 8.0), would be treated by the compiler as

public void Foo()
{
	DemoClass.<>c__DisplayClass0_0 CS$<>8__locals1;
	CS$<>8__locals1.internalValue = 20;
	DemoClass.<Foo>g__Bar|0_0(ref CS$<>8__locals1);
}
internal static void <Foo>g__Bar|0_0(ref DemoClass.<>c__DisplayClass0_0 A_0)
{
	Console.WriteLine("Do Something");
	int internalValue = A_0.internalValue;
	A_0.internalValue = internalValue + 1;
}

Notice how the compiler generates a struct to store the captured variables and pass it using `ref`. Such an overhead would be completely avoided when using the *static local functions*, simply because – the compiler won’t compile or capture the enclosing scope.

We explored two features introduced in C# 8.0. We will explore more in coming posts, possibly in bunches as we have plenty to catch up before exploring C# 9.0.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s