Version 6 of the .NET C# programming language was introduced when Visual Studio 2015 was recently released. What were the most significant improvements in the language?
The main focus of the release was the new compiler platform (known as ‘Roslyn’): There weren’t many obvious improvements or additions to the language as such, but the changes that I’ll mention in this article will definitely be appreciated by anyone who uses the C# programming language to develop applications.
Improvements in Auto-Properties
C# has ‘auto-implemented properties’ (or, more concisely, ‘auto-properties’) that make property-declaration for simple properties more concise. Behind the scenes, a private, anonymous backing field is created that can be read or updated only via the property’s get
and set
accessors. They are declared with the get
and set
keyword followed by a semicolon.
Initializers for Auto-Properties
It is now possible to declare the auto-properties just as you would a field:
1 2 3 4 5 |
public class Person { public string First { get; set; } = "Jane"; public string Last { get; set; } = "Doe"; } |
With this syntax, the initializer directly initializes the field that supports the property without actually using the set
accessor of the property.
The property initializers are executed in the order they are declared, just as and along with field initializers. After all, they are, in fact, field initializers.
As with the field initializers, property initializers cannot refer to this because like field initializers, they run before the object is properly initialized.
The implementation of this new functionality is actually syntactic sugar, in that it generates code that is compatible with previous versions of the .NET platform. In fact, the code I’ve used to illustrate the new syntax is translated by the compiler to the following C# code 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Person { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public string <First>k__BackingField = "Jane"; [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] public string <Last>k__BackingField = "Doe"; public string First { [CompilerGenerated] get { return <First>k__BackingField } [CompilerGenerated] set { <First>k__BackingField = value; } } public string Last { [CompilerGenerated] get { return <Last>k__BackingField } [CompilerGenerated] set { <Last>k__BackingField = value; } } } |
Note that the <First>k__BackingField
and <Last>k__BackingField
have names that are not valid C# names. This is done to ensure that there is no chance of collision between a name that is given by the programmer and a name given by the compiler.
Read-Only Auto-Properties
If you only declare a get
accessor, then the property is immutable everywhere except in its initializer and the constructor, so that it becomes read-only:
1 2 3 4 5 |
public class Person { public string First { get; } = "Jane"; public string Last { get; } = "Doe"; } |
In this case, the field is declared generated as readonly
(although this has importance only for the purpose of reflection).
As in the previous case, the generated code will be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Person { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly string <First>k__BackingField = "Jane"; [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly string <Last>k__BackingField = "Doe"; public string First { [CompilerGenerated] get { return <First>k__BackingField; } } public string Last { [CompilerGenerated] get { return <Last>k__BackingField; } } } |
As with the read-only fields, you can also initialize a read-only auto-property in the constructor.
1 2 3 4 5 6 7 8 9 10 |
public class Person { // ... public Person(string first, string last) { First = first; Last = last; } } |
And, once again, the equivalent compiler generates code C# 1:
1 2 3 4 5 6 7 8 9 10 |
public class Person { // ... public Person(string first, string last) { <First>k__BackingField = first; <Last>k__BackingField = last; } } |
Expression Bodied Function Members
We can now use the same convenient fat arrow expression syntax (=>
) to define lambda expressions to define function bodies.
Expression Bodied Method-Like Members
We use the lambda fat arrow (=>
) to define the body of methods as well as user-defined operators, type conversions and indexers. The expression to the right of the lambda arrow represents the body of the method.
1 2 3 |
public Point Move(int dx, int dy) => new Point(x + dx, y + dy); public static Complex operator +(Complex a, Complex b) => a.Add(b); public static implicit operator string (Person p) => p.First + " " + p.Last; |
The effect is exactly the same as if the methods have only a return statement. The above examples are translated by the compiler to:
1 2 3 4 5 6 7 8 9 10 11 12 |
public Point Move(int dx, int dy) { return new Point(x + dx, y + dy); } public static Complex operator +(Complex a, Complex b) { return a.Add(b); } public static implicit operator string (Person p) { return p.First + " " + p.Last; } |
For methods whose return type is void
(or Task for asynchronous methods), the lambda arrow (=>
) syntax still applies but the subsequent expression must be a statement (this is similar to what already happens with lambdas):
1 |
public void Print() => Console.WriteLine(First + " " + Last); |
…will be translated into…
1 2 3 4 |
public void Print() { Console.WriteLine(First + " " + Last); } |
Expression Bodied Property-Like Function Members
Expression bodies can also be used to define the body of properties and an indexers:
1 2 |
public string Name => First + " " + Last; public Person this[long id] => store.LookupPerson(id); |
Note the absence of the get
keyword, which instead is implied by the syntax of the expression.
The previous examples are translated by the compiler to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public string Name { get { return First + " " + Last; } } public Person this[long id] { get { return store.LookupPerson(id); } } |
The ‘using static’ Directive
We can now import the static or enumerated members of a class into our namespace. We can then use the static members of a class directly without qualifying them with their namespace or type name!
1 2 3 4 5 6 7 8 9 10 11 |
using static System.Console; using static System.Math; using static System.DayOfWeek; class Program { static void Main() { WriteLine(Sqrt(3 * 3 + 4 * 4)); WriteLine(Friday - Monday); } } |
The previous code is translated by the compiler to:
1 2 3 4 5 6 7 8 |
class Program { static void Main() { System.Console.WriteLine(System.Math.Sqrt(3 * 3 + 4 * 4)); System.Console.WriteLine(System.DayOfWeek.Friday - System.DayOfWeek.Monday); } } |
This feature is great when you have a set of related functions with a particular domain that is often used, such as System.Math
. We can reference the methods of the Math
class as though they were members of the class invoking them. It also allows you to specify the names of the members of an enumerated class as members of System.DayOfWeek
in the example above.
Extension Methods
Extension methods are invoked like regular static methods, (See: https://msdn.microsoft.com/en-gb/magazine/dn879355.aspx) but they are called as if they were instance methods on the extended type. Instead of bringing these methods to the current scope, the static import functionality makes these methods available as extension methods without the need to import all extension methods in a namespace like before:
1 2 3 4 5 6 7 8 9 10 |
using static System.Linq.Enumerable; // The type, not the namespace class Program { static void Main() { var range = Range(5, 17); // Ok: not extension var odd = Where(range, i => i % 2 == 1); // Error, not in scope var even = range.Where(i => i % 2 == 0); // Ok } } |
This means that it will now be a breaking change to change a normal static method into an extension method, which was not the case before. Extension methods are usually only invoked as static methods when there’s an ambiguity. And, in those cases, it seems legit to require full qualification anyway.
Null-Conditional Operator
It is often necessary to scatter null checks around code. Operators with null checks allow access to members and elements only when the receiver is not null, otherwise returning a null result:
1 2 |
int? length = people?.Length; // null if people is null Person first = people?[0]; // null if people is null |
The above code is translated into:
1 2 |
int? nullable = (people != null) ? new int?(people.Length) : null; Person person = (people != null) ? people[0] : null; |
The null-conditional operators can be very convenient when used with the coalesce operator (??)
:
1 |
int length = people?.Length ?? 0; // 0 se people é null |
The null-conditional operators have a short-circuit behaviour. In the chain of access to members, elements or invocations immediately adjacent will only run if the original recipient is not null:
1 |
int? first = people?[0].Orders.Count(); |
This example is, in essence, equivalent to:
1 |
int? first = (people != null) ? people[0].Orders.Count() : (int?)null; |
…except that people
is evaluated only once. None of the access to members or elements and invocations that follow the ? operator will run if the value of people
is null.
Nothing prevents null-conditional operators from being chained, if null verification is required more than once in the chain:
1 |
int? first = people?[0]?.Orders.Count(); |
In an invocation, a list of arguments in parentheses cannot be immediately preceded by the ‘?
‘ operator – It would lead to too many ambiguities. Therefore, the invoking of a delegate that one might expect if it is not null is not allowed. However, the delegate can always be invoked via its Invoke
method:
1 |
if (predicate?.Invoke(e) ?? false) { ... } |
A common use of this feature is the event trigger:
1 |
PropertyChanged?.Invoke(this, args); |
…which is translated to…
1 2 3 4 5 |
var handler = PropertyChanged; if (handler != null) { handler.Invoke(this, args); } |
This is a thread-safe way to see if the event has subscribers, because it only evaluates the left side of the invocation once, and keeps its value in a temporary variable. Without this feature, you used to have to write to this pattern yourself.
String Interpolation
The String.Format
method, and other similar methods, is very versatile and useful, but its use is a bit awkward and error-prone due to numerical markers ({0}
) that must match the position of the arguments supplied separately:
1 |
var s = string.Format("{0} is {1} year{{s}} old.", p.Name, p.Age); |
The interpolation syntax allows strings to directly replace the literal string indexes by “holes” with the expressions that correspond to the values:
1 |
var s = $"{p.Name} is {p.Age} year{{s}} old."; |
As with the String.format
method, it is possible to specify shapes and alignments:
1 |
var s = $"{p.Name,20} is {p.Age:D3} year{{s}} old."; |
The content of the holes can be any expression, including strings:
1 |
var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old."; |
Note that the conditional expression is in brackets, so that: “s” is not confused with the format specifier.
Formattable Strings
When not otherwise specified, a formatting provider uses the current culture of the current thread when invoking the String.Format
method, but this is not always what is wanted. That is why, as happens with lambda expressions, the compiler translates the interpolated string differently depending on the type of receptor expression.
If the receiver of expression is the IFormattable
type…
1 |
IFormattable christmas = $"{new DateTime(2015, 12, 25):f}"; |
…the compiler generates the following code:
1 |
IFormattable christmas = FormattableStringFactory.Create("{0:f}", new DateTime(2015, 12, 25)); |
This can be used as follows:
1 |
var christamasText = christmas.ToString(null, new CultureInfo("pt-PT")); |
FormattableString
The concrete type returned by FormattableStringFactory.Create
is derived from:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace System { public abstract class FormattableString : IFormattable { protected FormattableString(); public abstract int ArgumentCount { get; } public abstract string Format { get; } public static string Invariant(FormattableString formattable); public abstract object GetArgument(int index); public abstract object[] GetArguments(); public override string ToString(); public abstract string ToString(IFormatProvider formatProvider); } } |
This provides access not only the format but also to the format string arguments.
Backward Compatibility
The features introduced by the C# 6 are compatible with the previous .NET platforms. However, this particular feature requires System.Runtime.CompilerServices.FormattableStringFactory
and System.FormattableString
types that have been introduced only in version 4.6 of the platform. The good news is that the compiler is not tied to the location of these types in a particular assembly and, if we are to use this functionality in an earlier version of the platform, we just need to add the implementation of these types.
‘nameof’ Expressions
Occasionally it is necessary to provide a string with the name of some program elements.
- This can happen when throwing a
System.ArgumentNullException
; - Firing a
PropertyChanged
event; - And many other occasions.
We can use string literals for this, but they are error prone and are not changed when a name is changed by a code refactoring.
The nameof
expressions are a sort of literal of type string
in which the compiler validates the existence of something with that name. As it becomes a reference to the artefact, Visual Studio knows what it refers to and code navigation and refactoring work.
In essence, code such as the following:
1 2 |
if (x == null) throw new ArgumentNullException(nameof(x)); var s = nameof(person.Address.ZipCode); |
is converted to:
1 2 |
if (x == null) throw new ArgumentNullException("x"); var s = "ZipCode"; |
Source Code vs. Metadata
The names used by the compiler are the source names and not the metadata names of the artifacts, and so the following code…
1 2 3 4 5 6 7 8 9 |
using S = System.String; class C { void M<T>(S s) { var s1 = nameof(T); var s2 = nameof(S); } } |
…is converted to…
1 2 3 4 5 6 7 8 9 |
using S = System.String; class C { void M<T>(S s) { var s1 = "T"; var s2 = "S"; } } |
Primitive Types
It is not allowed to use primitive types (int
, long
, char
, bool
, string
, etc.) in nameof
expressions because they are not expressions and the argument of nameof
is an expression.
Add Extension Methods in Collection Initializers
When collection initializers were introduced in C#, the Add
method calls could not be an extension method invocation. Visual Basic allowed its use but it seemed to have been forgotten for C#.
In this version the fault has been corrected and it is now possible to use Add
extension methods in collection initializers.
It is a small but useful feature and, in terms of the implementation of the compiler, this was just the removal of the check of the condition that prevented it.
Index Initializers
Object and collection initializers are useful to initialize declaratively the fields and properties of objects or, in the case of collections, an initial set of elements.
Initializing dictionaries, on the other hand, were not so elegant, requiring the existence of an Add
method that received as an argument the key and the value corresponding to that key. If a dictionary implementation in particular did not have an Add
method with the aforementioned characteristics, it would not be possible to use an initializer.
From now on, it becomes possible to use initializers that use indexes:
1 2 3 4 5 6 |
var numbers = new Dictionary<int, string> { [7] = "sete", [9] = "nove", [13] = "treze" }; |
which will be translated into:
1 2 3 4 5 |
var dictionary = new Dictionary<int, string>(); dictionary[7] = "sete"; dictionary[9] = "nove"; dictionary[13] = "treze"; var numbers = dictionary; |
Exception Filters
Exception filters a CLR feature already provided by Visual Basic and F# and will now also be available in C#:
1 2 3 4 5 6 7 8 |
try { ... } catch (Exception ex) when (SomeFilter(ex)) { ... } |
If the evaluation of the expression in parentheses after the when
keyword evaluates to true
, the exception is caught. Otherwise, the catch
block is ignored.
This allows them to be laid over a catch
block for the same type of exception:
1 2 3 4 5 6 7 8 9 10 11 12 |
try { //... } catch (SqlException ex) when (ex.Number == 2) { // ... } catch (SqlException ex) { // ... } |
In the example above, the first catch block is only executed if an exception of type SqlException
in the value of the Number
property is 2. Otherwise you run the next block.
It is considered acceptable and commonplace “abuse” of exceptions filters with side effects such as logging.
WARNING: Exception filters are executed in the context of the throw
and not in the context of the catch
because the stack hasn’t been unwound yet.
‘await’ in ‘catch’ and ‘finally’ blocks
C# 5 was not allowed to use the await keyword in catch and finally blocks because, at the time of implementation of the async-functionality await, the team thought that this would not be possible to implement. But later, they found out that, after all, it was not impossible, although it can lead to some interesting semantics.
It becomes possible to write code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Resource res = null; try { res = await Resource.OpenAsync(); } catch (ResourceException e) { await Resource.LogAsync(res, e); } finally { if (res != null) await res.CloseAsync(); } |
Improvements in Overload Resolution Methods
They were introduced some improvements in overload method resolution (See: http://bc-programming.com/blogs/2015/06/c-6-features-improved-overload-resolution/) in order to make it more intuitive to determine how the compiler decides which method overloading to use.
Where it is more noticeable is the choice of method overloading when receiving nullable value types, or when moving a group of methods (rather than a lambda) methods for overload receiving delegates.
Resources:
Load comments