Building C# 8.0

<strong>C# 8.0 is on the horizon and will be here before you know it, bringing with it some interesting functionality. Let's take a peek into the future.</strong>

C# 8.0 is on the horizon and will be here before you know it, bringing with it some interesting functionality. Let's take a peek into the future.


Building C# 8.0

The next major version of C# is C# 8.0. It’s been in the works for quite some time, even as we built and shipped the minor releases C# 7.1, 7.2 and 7.3, and I’m quite excited about the new capabilities it will bring.

The current plan is that C# 8.0 will ship at the same time as .NET Core 3.0. However, the features will start to come alive with the previews of Visual Studio 2019 that we are working on. As those come out and you can start trying them out in earnest, we will provide a whole lot more detail about the individual features. The aim of this post is to give you an overview of what to expect, and a heads-up on where to expect it.


New features in C# 8.0

Here’s an overview of the most significant features slated for C# 8.0. There are a number of smaller improvements in the works as well, which will trickle out over the coming months.


Nullable reference types

The purpose of this feature is to help prevent the ubiquitous null reference exceptions that have riddled object-oriented programming for half a century now.

It stops you from putting null into ordinary reference types such as string – it makes those types non-nullable! It does so gently, with warnings, not errors. But on existing code there will be new warnings, so you have to opt in to using the feature (which you can do at the project, file or even source line level).

string s = null; // Warning: Assignment of null to non-nullable reference type

What if you do want null? Then you can use a nullable reference type, such as string?:

string? s = null; // Ok

When you try to use a nullable reference, you need to check it for null first. The compiler analyzes the flow of your code to see if a null value could make it to where you use it:

void M(string? s)
{
Console.WriteLine(s.Length); // Warning: Possible null reference exception
if (s != null)
{
Console.WriteLine(s.Length); // Ok: You won't get here if s is null
}
}

The upshot is that C# lets you express your “nullable intent”, and warns you when you don’t abide by it.


Async streams

The async/await feature of C# 5.0 lets you consume (and produce) asynchronous results in straightforward code, without callbacks:

async Task<int> GetBigResultAsync()
{
var result = await GetResultAsync();
if (result > 20) return result;
else return -1;
}

It is not so helpful if you want to consume (or produce) continuous streams of results, such as you might get from an IoT device or a cloud service. Async streams are there for that.

We introduce IAsyncEnumerable<T>, which is exactly what you’d expect; an asynchronous version of IEnumerable<T>. The language lets you await foreach over these to consume their elements, and yield return to them to produce elements.

async IAsyncEnumerable<int> GetBigResultsAsync()
{
await foreach (var result in GetResultsAsync())
{
if (result > 20) yield return result;
}
}

Ranges and indices

We’re adding a type Index, which can be used for indexing. You can create one from an int that counts from the beginning, or with a prefix ^ operator that counts from the end:

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

We’re also introducing a Range type, which consists of two Indexes, one for the start and one for the end, and can be written with a x..y range expression. You can then index with a Range in order to produce a slice:

var slice = a[i1..i2]; // { 3, 4, 5 }

Default implementations of interface members

Today, once you publish an interface it’s game over: you can’t add members to it without breaking all the existing implementers of it.

In C# 8.0 we let you provide a body for an interface member. Thus, if somebody doesn’t implement that member (perhaps because it wasn’t there yet when they wrote the code), they will just get the default implementation instead.

interface ILogger
{
void Log(LogLevel level, string message);
void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
public void Log(LogLevel level, string message) { ... }
// Log(Exception) gets default implementation
}

The ConsoleLogger class doesn’t have to implement the Log(Exception) overload of ILogger, because it is declared with a default implementation. Now you can add new members to existing public interfaces as long as you provide a default implementation for existing implementors to use.


Recursive patterns

We’re allowing patterns to contain other patterns:

IEnumerable<string> GetEnrollees()
{
foreach (var p in People)
{
if (p is Student { Graduated: false, Name: string name }) yield return name;
}
}

The pattern Student { Graduated: false, Name: string name } checks that the Person is a Student, then applies the constant pattern false to their Graduated property to see if they’re still enrolled, and the pattern string name to their Name property to get their name (if non-null). Thus, if p is a Student, has not graduated and has a non-null name, we yield return that name.


Switch expressions

Switch statements with patterns are quite powerful in C# 7.0, but can be cumbersome to write. Switch expressions are a “lightweight” version, where all the cases are expressions:

var area = figure switch
{
Line _ => 0,
Rectangle r => r.Width * r.Height,
Circle c => Math.PI * c.Radius * c.Radius,
_ => throw new UnknownFigureException(figure)
};

Target-typed new-expressions

In many cases, when you’re creating a new object, the type is already given from context. In those situations we’ll let you omit the type:

Point[] ps = { new (1, 4), new (3,-2), new (9, 5) }; // all Points

The implementation of this feature was contributed by a member of the community, Alireza Habibi. Thank you!


Platform dependencies

Most of the C# 8.0 language features will run on any version of .NET. However, a few of them have platform dependencies.

Async streams, indexers and ranges all rely on new framework types that will be part of .NET Standard 2.1. As Immo describes in his post Announcing .NET Standard 2.1, .NET Core 3.0 as well as Xamarin, Unity and Mono will all implement .NET Standard 2.1, but .NET Framework 4.8 will not. This means that the types required to use these features won’t be available when you target C# 8.0 to .NET Framework 4.8.

As always, the C# compiler is quite lenient about the types it depends on. If it can find types with the right names and shapes, it is happy to target them.

Default interface member implementations rely on new runtime enhancements, and we will not make those in the .NET Runtime 4.8 either. So this feature simply will not work on .NET Framework 4.8 and on older versions of .NET.

The need to keep the runtime stable has prevented us from implementing new language features in it for more than a decade. With the side-by-side and open-source nature of the modern runtimes, we feel that we can responsibly evolve them again, and do language design with that in mind. Scott explained in his Update on .NET Core 3.0 and .NET Framework 4.8 that .NET Framework is going to see less innovation in the future, instead focusing on stability and reliability. Given that, we think it is better for it to miss out on some language features than for nobody to get them.


How can I learn more?

The C# language design process is open source, and takes place in the github.com/dotnet/csharplang) repo. It can be a bit overwhelming and chaotic if you don’t follow along regularly. The heartbeat of language design is the language design meetings, which are captured in the C# Language Design Notes.

About a year ago I wrote a post Introducing Nullable Reference Types in C#. It should still be an informative read.

You can also watch videos such as The future of C# from Microsoft Build 2018, or What’s Coming to C#? from .NET Conf 2018, which showcase several of the features.

Kathleen has a great post laying out the plans for Visual Basic in .NET Core 3.0.

As we start releasing the features as part of Visual Studio 2019 previews, we will also publish much more detail about the individual features.

Personally I can’t wait to get them into the hands of all of you!

Happy hacking,

Mads Torgersen, Design Lead for C#


Learn More

C# Basics for Beginners - Learn C# Fundamentals by Coding

C# Intermediate: Classes, Interfaces and OOP

C# Advanced Topics: Take Your C# Skills to the Next Level

Learn to Code by Making Games - Complete C# Unity Developer

The Unreal Engine Developer Course - Learn C++ & Make Games

C++ Tutorial for Complete Beginners

Learn Advanced C++ Programming

C/C++ vs. Rust: A developer’s perspective

C/C++ vs. Rust: A developer’s perspective

In this post, you'll see the difference between Rust and C/C++ in a developer’s perspective

Originally published by Maourice Gonzalez at https://www.onmsft.com

C++ is an incredibly fast and efficient programming language. Its versatility knows no bounds and its maturity ensures support and reliability are second to none. Code developed in C++ is also extremely portable, all major operating systems support it. Many developers begin their coding journey with the language, and this is no coincidence. Being object-oriented means it does a very good job of teaching concepts like classes, inheritance, abstraction, encapsulation and polymorphism. Its concepts and syntax can be found in modern languages like C#, Java and Rust. It provides a great foundation that serves as a high speed on ramp to the more popular, easier to use and modern alternatives.

Now it’s not all rosy. C++ has a very steep learning curve and requires developers to apply best practices to the letter or risk ending up with unsafe and/or poor performing code. The small footprint of the standard library, while most times considered a benefit, also adds to the level of difficulty. This means successfully using C++ to create useful complex libraries and applications can be challenging. There is also very little offered in terms of memory management, developers must do this themselves. Novice programmers could end up with debugging nightmares as their lack of experience leads to memory corruption and other sticky situations. This last point has lead many companies to explore fast performing, safe and equally powerful alternatives to C++. For today’s Microsoft that means Rust.

The majority of vulnerabilities fixed and with a CVE [Common Vulnerabilities and Exposures] assigned are caused by developers inadvertently inserting memory corruption bugs into their C and C++ code - Gavin Thomas, Microsoft Security Response Center

Rust began as a personal project by a Mozilla employee named Graydon Hoare sometime in 2006. This ambitious project was in pre-release development for almost a decade, finally launching version 1.0 in May 2015. In what seems to be the blink of an eye it has stolen the hearts of hordes of developers going as far as being voted the most loved language four years straight since 2016 in the Stack Overflow Developer Survey.

The hard work has definitely paid off. The end result is very efficient language which is characteristically object oriented. The fact that it was designed to be syntactically similar to C++ makes it very easy to approach. But unlike the aforementioned it was also designed to be memory safe while also employing a form of memory management without the explicit use of garbage collection.

The ugly truth is software development is very much a trial and error endeavor. With that said Rust has gone above and beyond to help us debug our code. The compiler produces extremely intuitive and user friendly error messages along with great direct linking to relevant documentation to aid with troubleshooting. This means if the problem is not evident, most times the answer is a click away. I’ve found myself rarely having to fire up my browser to look for solutions outside of what the Rust compiler offers in terms of explanation and documentation.

Rust does not have a garbage collector but most times still allocates and release memory for you. It’s also designed to be memory safe, unlike C++ which very easily lets you get into trouble with dangling pointers and data races. In contrast Rust employs concepts which help you prevent and avoid such issues.

There are many other factors which have steered me away from C++ and onto Rust. But to be honest it has nothing to do with all the great stuff we’ve just explored. I came to Rust on a journey that began with WebAssembly. What started with me looking for a more efficient alternative to JavaScript for the web turned into figuring out just how powerful Rust turns out to be. From its seamless interop…

Automatically generate binding code between Rust, WebAssembly, and JavaScript APIs. Take advantage of libraries like web-sys that provide pre-packaged bindings for the entire web platform. – Rust website

To how fast and predictable its performance is. Everything in our lives evolves. Our smartphones, our cars, our home appliances, our own bodies. C++ while still incredibly powerful, fast and versatile can only take us so far. There is no harm in exploring alternatives, especially one as exceptional and with as much promise as Rust.

What do you guys think? Have you or would you give Rust a try? Let us know your thoughts in the comments section below.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Why you should move from Node.js to Rust in 2019

Rust Vs. Haskell: Which Language is Best for API Design?

7 reasons why you should learn Rust programming language in 2019

An introduction to Web Development with Rust for Node.js Developers

Why do C and C++ allow the expression (int) + 4*5?

Why is this&nbsp;<em>(adding a type with a value)</em>&nbsp;possible? (tried with g++ and gcc.)

(int) + 4*5;

Why is this (adding a type with a value) possible? (tried with g++ and gcc.)

I know that it doesn't make sense (and has no effect), but I want to know why this is possible.