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.