Recently Microsoft made several changes to the C# language. Some of those may even seem quite strange – definitely not your daddy’s C# anymore
The truth is, even though most of this is just “syntactic sugar” – has no equivalent in IL, in the same way that LINQ didn’t require any changes in IL too, the language is changing very rapidly. Of course, we don’t have to use all of the new features at once, but with time, we may come to the conclusion that some of them are really quite useful.
This article describes what changed in C# since version 7.0, which was released in the Fall of 2016, and also shows a glimpse of the future versions 7.3 and 8.0. Before 7 we had version 6, which was also feature-rich, but that was a long time ago.
Do not forget that in order to get access to the latest features, you will need to enable support for them in Visual Studio:
As you can see, you can pick any version you want, or you can use whatever the latest one is. The special versions default and latest resolve to the latest major and minor language versions installed on the machine, respectively.
Please keep in mind that features of unreleased versions – 7.3 and 8.0 – are possible to change before they are actually released, due to time or other constraints.
Version 7 has been around for quite some time, and I believe some of its features have already gotten into our development habits. Let’s see them one by one.
In the past, variables passed to an out parameter had to be declared first; now this is no longer the case, which results in slightly simpler code. Where we had to do this:
DateTime date; DateTime.TryParse(dateString, out date);
Now we can have instead:
DateTime.TryParse(dateString, out var date);
Notice two things: the date variable is now declared in the scope of the current method or property, and we can use the var keyword to automatically infer its type.
This one is probably everyone’s favorite: the ability to use tuples in C#. Tuples exist in other languages such as F# or Python, and are essentially a lightweight way to build types with just a couple of fields.
Tuples can have anonymous fields:
var temperature = (5, 41);
Or they can have names:
var temperature = (celsius: 5, fahrenheit: 41);
A variable can be declared without actually assigning it a value:
(double, double) temperature; // unnamed tuple (double celsius, double fahrenheit) temperature; // named tuple temperature.celsius = 5; temperature.Item2 = 41; //even for named tuples we can still use the ItemX notation
They can be used as the return type of methods:
public (double, double) GetTemperature()
Assigning a method return to a variable is called deconstruction:
(double celsius, double fahrenheit) = GetTemperature();
Depending on whether they are anonymous or not, its fields can be accessed directly, in the same way as a class or struct:
var celsius = temperature.celsius; // named tuple var fahrenheit: temperature.Item2; // unnamed tuple
Notice that in the case of tuples with unnamed fields, they get automatically called Item1, Item2 and so on.
Of course, you can declare extension methods too:
public static (double, double) IncrementCelsius(this (double celsius, double fahrenheit) temperature, double celsiusDegrees) { return (temperature.celsius + celsiusDegrees, ((temperature.celsius + celsiusDegrees) * 9.5 + 32)); }
Finally, you can also assign named tuples to unnamed ones, provided you respect the field types:
(double, double) temperature; (double celsius, double fahrenheit) temp2 = temperature;
Another aspect is, you can “deconstruct” any type into a tuple. For that, you need to provide a Deconstruct method with the appropriate parameters. For example, consider this class:
public class Location { public int X { get; set; } public int Y { get; set; } public void Deconstruct(out int x, out int y) { x = this.X; y = this.Y; } }
You can then do:
var loc = new Location { X = 10, Y = 20 }; var (x, y) = loc;
And the tuple will be populated automatically! You can have any number of Deconstruct methods you want, as long as their signature is different, this will allow deconstructing to different tuple structures.
So, what are tuples useful for; can’t we just use classes or structs instead? We sure can, but this way it’s easier as we don’t have to declare the type. Just don’t forget that we need to add the System.ValueTuple NuGet package.
Discard variables are just that: variables that you don’t care about. You can pass them inside tuples, or for out parameters:
var (celsius, _) = GetTemperature(); if (DateTime.TryParse(dateString, out var _) { }
Keep in mind that you can’t declare multiple discard (_) variables on the same scope, but you can do so in the same tuple deconstruction.
The ref keyword can be used to return values from a method. We have other alternatives, such as out parameters or tuples, but ref has been around since the beginning of .NET and is especially useful if the return value is conditional, for example, if a conversion cannot be done. Now we also have return ref types.
Return ref values are particularly useful if we wish to return a pointer to an array position:
private int[] _array; public ref int ArrayValue(int index) { return ref _array[index]; } ref var val = ref ArrayValue(1); val++;
Notice the multiple uses of the ref keyword: not just in the method, but also on the variable declaration and assignment. Also, the declaration of a ref variable must be followed by an assignment of a ref method call, in pretty much the same way as a var declaration.
It is now possible to declare functions inside methods, property bodies, or even constructors. Why is that, I hear you ask; aren’t lambdas enough? Well, lambdas cannot have attributes or ref/out parameters. Local functions are just like any other type-level method, but only with a more narrow scope.
public static void Main() { int Add(int a, int b) => return a + b; bool Divide(int a, int b, out double result) { if (b != 0) { result = a / b; return true; } result = 0; return false; } var result1 = Add(1, 2); Divide(1, 2, out var result); }
Remember expression-body properties? They allow you to return simple (or not so simple) expressions without the drag of { }:
private DateTime _date; public DateTime Date => _date;
Now you can do the same with get or set property, methods, constructors, and finalizers:
public DateTime Birthday { get => _birthday; set => _birthday = value; } public int Age() => (int) (DateTime.Today - _birthday).TotalDays / 365; public Person(string name) => this.Name = name; ~Person() => Console.WriteLine("Finalizer called");
This is just a simplified syntax, nothing really new here, but still nice to have.
The syntax for the switch keyword and for comparisons has been greatly enhanced; whereas before all we could do was exact matching, now we have additional patterns that we can check for:
object o = 100; if (o is int val) { } else if (o is null) { }
As you can see, we can declare a variable that will hold the cast value, in case it is of the specified type.
For switches, it gets even better, as we can add a condition to it:
switch (o) { case int even when (even % 2) == 0: break; case int odd: break; }
We can now use throw expressions in lambdas or expression bodies:
Func<int, int, double> result = (dividend, divisor) => divisor == 0 ? throw new ArgumentException("Divisor cannot be null") : dividend / divisor; public int Property { get: return _property; set: _property = (value < 0) ? throw new ArgumentException("Value cannot be negative") : value; }
Sometimes an async method returns a value type, which does not need to be allocated on the heap. But because Task and Task<T> classes are reference types, they are always allocated there. Realizing this, Microsoft introduced the ValueTask<T> struct, which provides a better alternative. The syntax is exactly the same:
public async ValueTask GetTemperatureCelsius() { return 5; }
There’s a constructor in ValueTask<T> that takes a Task<T> parameter, this is how you can return values coming from “legacy” asynchronous methods:
public async Task GetTemperatureAsync() { } return new ValueTask(GetTemperatureAsync());
You’ll need to add a reference to the System.Threading.Tasks.Extensions NuGet package.
This one is really only about readability: we can use the _ character to separate digits in any of the numeric formats we have:
var pi = 3.141_592_653_589; var sixtyFour = 0b0100_0000; var billion = 1_000_000_000;
Remember that this is just for visualization, it doesn’t impact at all the actual value being used.
This one does not bring as much improvements as 7.0, but here they are.
The Main method can now be made asynchronous by adding the async keyword. If you combine it with the expression body members feature, you can have it like this:
static async Task Main() => await Something();
Interestingly, as of now, you can return both Task or Task<int>, but not ValueTask<int>. Anyway, with this approach, you can go asynchronous all the way!
Sometimes, returning the default value for a generic type can be tedious. For example, consider this:
Func<int, int, int> operation = default(Func<int, int, int>);
Now you can just write:
Func<int, int, int> operation = default;
This also works with parameters too, of course, and with simple types:
public int Increment(int value, int amount = default) => value + amount; int i = default;
This one is a small improvement, and works in pretty much the same way as anonymous types: it can infer tuple members from the variable names being passed. For example, say you have this:
string name = "Ricardo"; int yearOfBirth = 1975;
You create a tuple like this:
var data = (name, yearOfBirth);
And the tuple member names will be called, respectively, name and yearOfBirth. Note that, of course, this only works when you use variables or properties to initialize the tuple, not methods or complex expressions.
Another small number of changes, mostly related to performance.
A couple of changes were introduced that aim to provide better performance with structs (which are copied byte by byte, remember). The changes are:
double Distance(in Point p1, in Point p2)
Point _currentPos; ref readonly Point GetCurrentPosition() => ref _currentPos; ref readonly Point pos = ref GetCurrentPosition();
readonly struct ImmutableLocation { public ImmutableLocation(int x, int y) { X = x; Y = y; } public int X { get; } public int Y { get; } }
public readonly ref struct Span { ... } Span bytes = ...; object pointer = byes; // does not compile public struct ManagedStruct { public Span Bytes { get; set; } // does not compile } public ref struct ManagedRefStruct { public Span Bytes { get; set; } //ok }
Some of these modifiers have bigger implications, so I advise you have a look at the official documentation here.
Before this version, we could only declare named arguments after all mandatory ones had values. Now it is no longer the case, and we can have arguments in any order:
public static int Sum(int a, int b) => a + b; Sum(1, b: 2);
Read all about named and optional arguments here.
Version 7.1 introduced underscores in numeric literals, but left out the capability to add them as the first character. This functionality now brings this to C#.
var byteValue = 0b_1111_0000;
Private protected members existed since the specification of the .NET CLR, but no .NET language has had support for it, until now. Essentially, this accessibility level lets derived types access the member, but only if they belong in the same containing assembly. See the other access modifiers here.
Seems weird, but this is now possible:
public struct MyStruct { } public static class StructExtensions { public static void Update(ref this MyStruct r) { r = new MyStruct(); // replaces the original variable's value } }
This is thus valid code:
var x = new MyStruct(); x.Update(); // x now becomes something else!
This works for any struct, not just ref structs, but, alas, won’t work with reference types.
This version will be available with Visual Studio 17 15.7, which is currently in preview, so we can already test it. Again, some features are related to performance, as it’s now a very hot topic at Microsoft.
You may be aware that auto properties which have been around for quite a while have indeed a backing field to support them. But before this feature, we could not apply attributes to these backing fields, only to the properties themselves. Now this will be possible:
[field: NonSerialized] public string Password { get; set; }
This feature introduces a new constraint for generic parameters: unmanaged. This will be usable where new(), class and struct are, but we won’t be able to combine it with any of them.
void Process(T [] data) where T : unmanaged { }
This will only be useful for the few of us working with unmanaged memory. T itself has to be a struct and its fields can only consist of:
Two additional new constraints are enum and delegate, which do exactly what they seem: allow only enumerated or delegate types to be passed as generic parameters.
void Call(T action) where T : delegate { } T Parse(string enumeratedValue) where T : enum { }
This is a complement to the initializer syntax for constructors, methods, fields and properties where out/ref could not be used. Now, this is valid syntax:
public bool IsValidDate(string str) => DateTime.TryParse(str, out var date);
It shall be possible to reassign values to ref variables, even inside loops:
ref VeryLargeStruct reflocal = ref veryLargeStruct;
Three new rules for compiler overload disambiguation:
This one is tricky and can be regarded as a relaxation of the specification. Essentially, it won’t be necessary to pin (fix) explicitly variables that point to fixed fields:
unsafe struct S { public fixed int myFixedField[10]; } class Program { static S s; unsafe static void Main() { var p = s.myFixedField[5]; // no fixed required } }
Don’t worry too much about this one, unless you are working with unmanaged code and fixed memory locations.
A fixed statement, which prevents the garbage collector from claiming a heap allocation, can now be used as this:
fixed (byte* ptr = GetUnmanagedPointer()) { // something }
Again, a feature only helpful for developers working with unmanaged code.
This will now be possible:
stackalloc int[3] { 1, 2, 3 };
Because tuples (introduced in C# 7.0) are automatically generated, they do not implement the == and != operators, which means the compiler relies on reference equality (same object). Or, they don’t, until this feature is implemented, when it does, this will work:
var equal = (10, "abc") == (20, "xyz"); // false
What will happen is, every field will be compared for equality/inequality, and only if they are all considered equal will the test for the tuple succeed.
This is the next future major version of .NET. It is still unclear when it will be released – not this year, for sure – but it’s an ambitious one and one likely to cause some controversy, especially because of nullable reference types. Lots of new features, but let’s go one by one.
This feature will make it possible to use partial and ref keywords in any position, in a type declaration:
public class partial MyClass { } public ref struct MyStruct { }
This one may not be very easy to understand and only a few will be able to use it. The stackalloc keyword allows the allocation of a block of memory on the stack, when used in unsafe C# code. This currently can only be used in local variable initializers, but this feature will allow its usage in other places as well.
Another generic parameter constraint, this time, for enforcing the parameter to be nullable. It will go somewhat like this:
public class GenericContainer where T is null { public virtual T GetItem() => null; }
It’s slightly better than returning default, among other reasons, because the default value of a type other than a reference one is not always easy to compare and null is a very specific value.
Another feature related to tuples, this time, allowing the default keyword when declaring a tuple:
(int x, int y) = default;
If this one comes to life, we will have a syntax similar to Pascal’s for defining ranges:
var array = new int[SIZE]; var slice = array[500:1000];
Notice that the actual syntax may change!
When this feature is implemented, it shall be possible to declare integer types of the architecture’s “natural” size (e.g., 32 bytes for x86, 64 for x64). A new type (or types, because of unsigned) nint will be introduced. This will result in better performance in some cases and we won’t have to know about the architecture where the code is running.
This feature will permit indexing fixed-size buffers in managed code. The declaration will be like this:
public fixed UNMANAGED_STRUCTURE Buffer[1025];
A controversial proposal to add negated semantics to if statements:
if !(condition) { }
or an alternative syntax:
if not (condition) { }
Again, this is not something that we can’t achieve right now.
Pattern-matching (introduced in C# 7.0 via the is keyword) will be augmented, for types:
if (expression is MyType) { }
for constant values (literals, types, enumerated values, nulls):
if (expression is null) { }
for variable extraction:
if (expression is var int) { }
This one will make it possible to replace code like this:
if (variable == null) { variable = expression; }
By this:
variable ??= expression;
C# 6 introduced the null-conditional operator:
myVar?.MyMethod();
If you remember, MyMethod will only be called if myVar is not null. This proposal brings the same to awaitable variables, the syntax might look like this:
await? task;
Another code saver: this will prevent unambiguous instantiations to skip the type name, like this:
Point p = new (10, 10);
When working with tuples, the following will be permitted:
var x; (var w, x) = (0, 0);
Notice how we are mixing a variable (x) with a new declaration (w).
This feature will permit converting from nullable int into nullable double without the need for any cast, making this code compile:
int? nullableInt = 10; double floating = 5; var result = condition ? nullableInt : floating;
Not huge, just making the compiler somewhat smarter and sparing us some code.
Similar to the previous future, here’s another code saver. When we have a ternary expression in the form of:
var myVariable = condition ? null : 100;
we currently must add an explicit cast to null so that the compiler can infer the type of the implicit variable we’re declaring:
var myVariable = condition ? (int?) null : 100;
In this case, as the value is null, we need it to be a reference (or nullable) type. This proposal makes the compiler somewhat smarter, in that it will be able to guess what we want and declare myVariable to be nullable.
It shall be possible to declare variables in a method call expression and use them in the same scope:
if (M((var x = expr).a, x.b) { }
For dictionaries (Dictionary<TKey, TValue>) we will have a special syntax:
var x = ["foo": 4, "bar": 5];
Yes, it’s true that we already had a syntax for any collection that offered an Add method, but this one is very specific to dictionaries, and it’s easier to understand.
.NET’s strings (the String class) use UTF-16 encoding. This proposal will make it possible to use UTF-8 for constants and maybe some APIs will have overloads to support it. The main reason for this is space, e.g., a UTF-8 take half the size of its UTF-16 counterpart. Just beware storing national characters in it, as most will not be supported.
We already had this in VB and JavaScript, now we will have it in C#: the with expression will somewhat simplify assigning values to fields or properties of some variable. The proposed syntax is like this:
with (point) { x = 10; y = 20; }
Type classes, shapes or concepts exist in several languages and this proposal aims to bring them to C# as well. It will make it possible to define operations and operators for generic types that match a given constraint. This can be regarded as a general-purpose mechanism for adding extensions (not just methods) to classes. The actual syntax may change, but here is a simple example:
public shape SNumber<T> { static T operator + (T t1, T t2); static T Zero { get; } static implicit operator bool (T t1); } public extension IntGroup of int : SNumber { public static int Zero => 0; public static int operator + (int t1, int t2) => t1 + t2; // this one is actually inferred automatically public static implicit operator bool (int t1) => t1 != 0; }
This example shows a shape (SNumber<T>) and an actual extension of it applied to type int. The shape introduced the concepts of zero, addition, and conversion to boolean, and the extension applied them for the integer type.
This originated from Java is is likely to raise some controversy. As you know, unlike abstract classes, interfaces cannot have constructor, method, property or field declarations; adding a method declaration to an interface breaks existing code, as they do not have an implementation for it. This proposal allows adding methods with a default implementation to existing interfaces, and these methods are treated as virtual, which means that implementing classes can change the implementation. It’s true that we already had extension methods, but these are not really part of the type they’re extending, which means that we can’t list them using reflection, for example. Imagine we had an interface like this:
public interface IMyService { }
Later on we could extend it by having it implement IDisposable, but then we would need to provide a default implementation for its Dispose method:
public interface IMyService : IDisposable { public void Dispose() { /*do nothing by default*/ } }
As you can see, these are just method declarations with a body, like we have in regular types. The Dispose method can be overridden in implementing classes.
Up until now, if we had declared a constructor in a struct, we had to initialize every field/property members from it. Also, we could not initialize field/property members in their declarations, e.g., this wasn’t valid C# code:
public struct Complex { public Complex() { } public double Img { get; set; } = 0; public double Real { get; set; } = 0; }
Some of you will be happy to know that this is now possible, which brings structs in sync with classes.
C# 8 will introduce a more concise syntax for defining types with just properties (records):
class Point(int X, int Y)
This will result in a class with properties X and Y of the int type. Moreover, the compiler will implement IEquatable<T> and the Equals and GetHashCode methods for us. Essentially, a simplified declaration, the record type will behave just like any other type.
Tony Hoare invented the null concept back in the 60’s. After that, he famously called it his “billion-dollar mistake”. Why is that? Well, null references do lead to a big number of crashes. How would you feel about having a programming language without nulls?
This is probably the most debatable new feature in C# 8. The idea is that, like already happens with value types, reference types cannot have the null value by design. If you wish to have a reference type variable assigned to null, you will have to declare it in a different way explicitly. The way to mark a reference type as being nullable is the same as for value types, with the ? character.
So, this won’t be allowed:
MyReferenceType builder = null;
You will need to use this instead:
MyReferenceType? builder = null;
This will apply only to new reference types, declared in your own assemblies; types declared in external references will continue to work the same way, this is so that your code still compiles.
Keep in mind that this will be an opt-in decision, that is, you may continue with the old behavior of allowing nullable reference types by default.
Microsoft believes that this will result in better code, with less bugs. At least, we can give it a try!
This one is cool, we will be able to write code like this:
foreach (await var item in ae.GetEnumerator(cancellationToken)) { ... }
There will be new interfaces (and matching types) for asynchronous traversal of lists (IAsyncEnumerable<T>, IAsyncEnumerator<T>) and also asynchronous disposal (IAsyncDisposable).
As you can see, C# evolved quite a bit and some of its new features would seem totally unbelievable just a couple years ago. In general, we’re talking about improvements to the language, we have not constrained any way to use them, but here and there they do offer interesting new options. I guess time will tell what exactly is useful and what isn’t, but, in the meantime, we may as well get used to them. As you can see, quite a lot of them are related with performance and unmanaged/unsafe code, which goes to say, they are not targeted at the average developer. Oh, and by the way, until the features are closed, you can make yourself heard: do share your thoughts with the Microsoft team, you can make a difference!
With APM, server health metrics, and error log integration, improve your application performance with Stackify Retrace. Try your free two week trial today
Pre-release Visual Studio 2017
3 New C# 8 Features We Are Excited About
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]