Sneak peek at C# 8

Mads Torgersen demonstrated the first four features for C# 8 in a recent Channel 9 video. C# 7 was shipped in March 2017 and version 7.1 was released in August 2017. The three languages features that were introduced in 7.1 are:

The C# compiler supports C# 7.1 starting with Visual Studio 2017 version 15.3, or the .NET Core SDK 2.0. However, the 7.1 features are turned off by default. To enable the 7.1 features, you need to change the language version setting for your project. You can edit the “csproj” file and add or modify the following lines:

<PropertyGroup>

<LangVersion>latest</LangVersion>

</PropertyGroup>

You can also change the project properties in Visual Studio. In Visual Studio, right-click on the project node in Solution Explorer and select Properties. Select the Build tab and select the Advanced button. In the dropdown, select C# latest minor version (latest), or the specific version C# 7.1.

The latest value means you want to use the latest minor version on the current machine. The C# 7.1 means that you want to use C# 7.1, even after newer minor versions are released.

There is also a C# version 7.2 lined up with features such as ref readonly, blittable, interior pointer and non-trailing named arguments.

The language design teams meets twice a week, and have two-hour long discussions on the various issues related to adding new features to the language. There are a lot of proposals to keep track of here: https://github.com/dotnet/csharplang/tree/master/proposals. While it is interesting to see what will be added, given that these are proposals and there’s plenty of debate around the implications, there’s no guarantee that the proposed features will actually make it in the upcoming version(s).

For version 8 some features are in prototype status, some are just concepts. The first four features that should make it to version 8 of C# are:

  • Nullable Reference Types
  • Async Streams
  • Default Interface Methods
  • Extension Everything

Nullable reference types

This feature is intended to:

  • Allow developers to express whether a variable, parameter or result of a reference type is intended to be null or not.
  • Provide optional warnings when such variables, parameters and results are not used according to their intent.

With nullable reference types, the idea is that reference types would no longer be nullable by default. Instead, you have to explicitly mark them as nullable using the same T? syntax that you use for nullable value types.

Assigning a null to a non-nullable reference type will be a compiler warning. Reading from a nullable type would be a compiler warning unless the variable in question was explicitly checked for null ahead of time. So theoretically the only change developers need to make is to sprinkle question marks where appropriate. There may be cases where you know that the nullable variable x isn’t actually null, but the compiler cannot figure it out, perhaps because your method of checking for null is deeply nested somewhere. In this case you can use x!.YourMethod() to suppress the compiler warning about potential null reference exceptions.

Async Streams

Async streams are the asynchronous equivalent of IEnumerable. C# has support for iterator methods and async methods, but no support for a method that is both an iterator and an async method. Work to improve this situation has been going on since 2015. The solution is allowing for await to be used in a new form of async iterator, one that returns an IAsyncEnumerable<T> or IAsyncEnumerator<T> rather than an IEnumerable<T> or IEnumerator<T>, with IAsyncEnumerable<T> consumable in a new foreach await. An IAsyncDisposable interface is also used to enable asynchronous cleanup.
The syntax looks like this:

foreach await (string s in asyncStream)

When defining an async iterator, you would use this function signature:


async IAsyncEnumerable<T> MethodName()

As with normal IEnumerable methods, you can use yield return to build the stream of objects in a lazy fashion.

The benefit of using this instead of IObservable<T> from Reactive Extensions is that the consumer controls the flow rate. This is referred to as a “pull model”. By contrast, IObservable<T> is a “push model”, which means the producer can flood the consumer with a higher flow rate than it can handle.

You may wonder if this will also work with LINQ. We have to consider that there are over 200 overloads of methods on the System.Linq.Enumerable class, all of which work in terms of IEnumerable<T>. Some of these accept IEnumerable<T>, some of them produce IEnumerable<T>, and many do both. Adding LINQ support for IAsyncEnumerable<T> would likely entail duplicating all of these overloads for it, for another 200. And since IAsyncEnumerator<T> is likely to be more common as a standalone entity in the asynchronous world than IEnumerator<T> is in the synchronous world, we could potentially need another 200 overloads that work with IAsyncEnumerator<T>. Plus, a large number of the overloads deal with predicates (e.g. where that takes a Func<T, bool>), and it may be desirable to have IAsyncEnumerable<T>-based overloads that deal with both synchronous and asynchronous predicates (e.g. Func<T, Task<bool>> in addition to Func<T, bool>). While this isn’t applicable to all of the now over 400 new overloads, a rough calculation is that it would be applicable to half, which means another 200 overloads, for a total of about 600 new methods.

That is a huge number of APIs, with the potential for even more when extension libraries like Interactive Extensions (Ix) is considered. But Ix, as a part of Reactive Extensions, already has an implementation of many of these, and there doesn’t seem to be a great reason to duplicate that work; So the idea is that Ix will need some improvement and then it can be recommended for when developers want to use LINQ with IAsyncEnumerable<T>.

Default Interface Methods

Default interface implementations are essentially a limited form of multiple-inheritance. This will allow abstract interfaces to fully define methods just like abstract classes. However, abstract interfaces will still not be able to declare constructors or fields. Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.This isn’t guaranteed, as it would only work when a suitable default method can be devised. The feature enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift), which support similar features. This proposal requires a coordinated update to the CLR specification (to support concrete methods in interfaces and method resolution).

The most cited use case for this feature is the ability to add a Count property to IEnumerable<T>. The idea is that instead of using the Enumerable.Count extension method, developers can get Count for free and optionally override it if they can provide a more efficient alternative.

interface IEnumerable<T>
{
int Count()
{
int count = 0;
foreach (var x in this)

count++;

return count;

}

}

interface IList<T> ...

{

int Count { get; }

override int IEnumerable<T>.Count() => this.Count;

}

This is a very debated feature. Check out the conversation on https://github.com/dotnet/csharplang/issues/406.

Extension Everything

Exension methods was introduced in C# 3 as part of LINQ. At the time, only extension methods was really necessary to enable the LINQ features. Some may argue that extension properties would have been nice as well, to avoid having a Count() method which should have been the property Count. As a result, when implementing extension methods, it led the design team to a path where there is no way to even define an extension property or event using the proposed pattern. Extension methods are defined in a static class with methods that reference this as a first parameter. Properties don’t have parameters, so it not possible to use this syntax to make it work for properties. With a new design, there is a new top-level construct called an “extension”. For example, if you want to create extension methods and properties for an Person class you would write:

extension PersonExt extends Person {

//methods and properties go here

}

While you may think this can be accomplished with a long time (compiler) feature of partial classes, it’s not the same. Partial classes need to be in the same assembly, and the compiler simply merges the code together to form a single class. Extension properties, as an example, will not modify the class definition and can live in a separate assembly.

It’s important to note that these properties do not contain state in and by themselves. You, as the developer, need to build that yourself and as with interfaces, you cannot define instance fields in extensions, but you can simulate them using ConditionalWeakTable. You can also define static fields and operators. In addition to properties, events, and operator overloads, they are even considering allowing extension constructors. Extension constructors would be practical in factory and object pooling scenarios.

For more information about the future of C#, check out the C# Language Design Repo and the proposals section in particular.

Leave a Reply

Your email address will not be published. Required fields are marked *