If you haven't heard, C# 8.0 is not supported on anything below .NET Core 3. This is in part due to the fact that some of the features cannot run on runtimes below .NET Core 3, and rather than complicate things, C# 8.0 is considered an all-or-nothing choice.
This presents a bit of a problem, because you might be tempted to use some the compelling new features in a lower target, particularly if you are multi-targeting. For example, a lot of people are going to want to additionally target
netstandard2.0 for a broader reach, probably at least up until November 2020 when .NET 5 is due.
Here is a slide a from by Mads Torgersen at .NET Conf, saying that for library authors you want to consider targeting both
netstandard2.0, and use C# 8.0 to get "nullified" during this "Nullable adoption phase". You can catch the full video here.
So the good news is that you can enable C# 8.0 on other targets, however it is "unsupported" i.e. this is probably not a good idea, but if you don't mind dealing with some hassle, let's do this! 🤠
Types of Language Features
The new language features can roughly be viewed as being in one of 3 categories:
- Syntax Only - Language features that are purely syntax will "Just Work™".
- Requires Types - Some language features rely on new types that are all present in .NET Core 3; if we are going to use these features we need to satisfy the compiler and runtime. Sometimes the shape of a type is enough to light up a feature, sometimes it needs to be a particular name, namespace and implementation.
- Requires Runtime Support - If the feature needs to utilise a new runtime feature that ain't there, it just ain't gonna work!
C# 8.0 Feature Compatibility
|Static local functions||1|
|Disposable ref structs||1|
|Nullable reference types||1 (and a little bit of 2)|
|Indices and ranges||2|
|Default interface members||3 (Will not work 💥)|
So you can see, categorising the new features, there's a lot we can use "for free" whilst in this unsupported mode. In particular, two features I'd encourage anyone to adopt are switch expressions and using declarations. Here's a good example:
Feature Break Down
Default Interface Members
This feature requires a runtime that supports it, so if you are targeting a runtime that doesn't have that support, it's never going to work. The compiler helps us out here by giving us a clear error message if we try:
Async streams is the ability to use
IAsyncEnumerable at the language level. One way of doing this is with
await foreach, the other is with async iterator methods that let you
To use this feature in
netstandard2.0, you can pull in a reference to this package:
This package defines the
IAsyncEnumerable interface for platforms that don't officially support C# 8.0. This is a really big deal, because it means you can start exposing this type today in your library APIs and know that people can reasonably consume it from earlier Target Framework Monikers (TFMs).
Indices and Ranges
Indices and Ranges is a lovely little feature, for example it lets us trivially slice things like this:
On the surface, it may appear to be syntax only, but this compiles down to use two types,
Range. The compiler looks for these by fully qualified name, so all we need to do is create those types in our project and it will work.
While this does work, potentially there is a problem if we expose one of these types in our public API. Consuming libraries will see
Range in the
System namespace, but that would clash with their own implementations, or the implementation from supported TFMs.
To solve this, we could put these types in a NuGet package and type forward to the real implementations on the supported runtimes. I have done this here:
However I have not distributed it yet, as for it to be generally useful for the community, it must be the canonical package used amongst consumers that want to interoperate with it, and I want to think about having maintained in a more central place.
This library also contains extension methods to types like
String to match those in the supported TFMs, as that is where the syntax is most useful.
Nullable Reference Types
This is the big one! If you are still reading this, odds are you are looking because you want to know about Nullable Reference Types.
The way this feature works is by the compiler emitting attributes on methods, parameters, fields etc... indicating their nullability. These attributes (
NullableContext) are actually emitted by the compiler as
internal types and are placed in your assembly. So this "Just works™"!
However, there is another useful subset of this feature that you might want to use, and that is the attributes that you would add to indicate the relationship of nullability between method parameters and return values. An often used example for this is the familiar
string.IsNullOrEmpty, here an attribute is used to say that when this method returns
true the parameter cannot have been
null. This allows for more accurate flow analysis and makes this language feature more usable. You can find find these under
System.Diagnostics.CodeAnalysis, and only exist for supported TFMs.
You can however reference this NuGet package by Manuel Römer:
This package is a source package, so it will include these nullable attributes as source at build time. The nice thing about this is that you can reference the package in your csproj with
PrivateAssets="all", which will mean that this doesn't become a dependency for anyone depending on your code.
To be clear, you don't need this package, but it will come in handy from time to time, and with this package you do not need to compromise while targeting
netstandard2.0 for example.
An alternative to this package is ReferenceAssemblyAnnotator, this also provides the attributes and additionally weaves in nullability into .NET Framework and .NET Standard reference assemblies.
For a deeper dive into Nullable Reference Types, you should check out Cezary Piątek's article here.
If you take away one thing from this post, please let it be this:
While Nullable Reference Types as a feature is "not supported" in lower TFMs such as
netstandard2.0, it is encouraged to use this feature, and you can do it with little hassle.
Putting some of these thing together, I managed to get some C# 8.0 features running under mono targeting
net461 on my mac, you can see that here:
If there is anything I've missed, please let me know in the comments.