With Visual Studio 2017, Microsoft increased the release cadence for C#.
Between the major versions, which were historically aligned with new Visual Studio versions, they started to release minor versions as part of selected Visual Studio 2017 updates. Minor versions will include smaller new features, which don’t require changes to the Common Language Runtime (CLR).
Larger features will still be released with major versions only.
Are you keeping up with new developer technologies? Advance your IT career with our Free Developer magazines covering C#, Patterns, .NET Core, MVC, Azure, Angular, React, and more. Subscribe to the DotNetCurry (DNC) Magazine for FREE and download all previous, current and upcoming editions.
C# 7.1 – What’s New
C# 7.1 was released in August 2017 as part of the 15.3 update for Visual Studio 2017. Unlike new language releases in the past, this time the new features are not automatically enabled after updating Visual Studio; neither in existing projects, nor when creating a new project.
If we try to use a new language feature, the resulting build error will suggest upgrading the language version in use.
Image 1: Build error for new language features
The language version can be changed in the project properties.
On the Build tab there is an Advanced button, which will open a dialog with a dropdown for selecting the language version. By default, the latest major version is selected, which is 7.0 at the moment.
Editorial Note:If you are new to C# 7, read our tutorial at www.dotnetcurry.com/csharp/1286/csharp-7-new-expected-features
We can select a specific version instead (7.1 to get the new features) or the latest minor version, which will always automatically use the latest version currently available.
Image 2: Changing the language version
The latter option is not selected by default.
This is so that development teams can control how they will adopt new minor language versions. If new language features were automatically available, this would force everyone in the team to update Visual Studio as soon as a single new feature was used for the first time or the code for the project would not compile.
The selected language version is saved in the project file and is not only project specific, but also configuration specific.
Thus when changing the language version in the project properties, make sure you do it for each configuration, or even better: set the Configuration on the Build tab to All Configurations before applying the change.
Otherwise you might end up changing the language version for Debug configuration only, causing the build to fail for Release configuration.
Image 3: Configuration selection on Build tab
For some language features, there is also a code fix available, which will change the language version to 7.1 or to the latest minor version. It will automatically do it for all configurations.
Image 4: Code fix for changing the language version
Four new language features were introduced in C# 7.1
Async Main
Support for asynchronous Main function was already considered for C# 7.0, but was postponed until C# 7.1. The feature simplifies using asynchronous methods with async and await syntax from console applications. Before C# 7.1, the Main method as the program entry point supported the following signatures:
public static void Main();
public static int Main();
public static void Main(string[] args);
public static int Main(string[] args);
As asynchronous methods can only be awaited when called from inside other asynchronous methods, this required additional boilerplate code to make it work:
static void Main(string[] args)
{
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
// asynchronous code
}
With C# 7.1, Main method supports additional signatures for asynchronous code:
public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);
When using one of the new signatures, asynchronous methods can be awaited directly inside the Main method. The compiler will generate the necessary boilerplate code for them to work.
Default Literal Expressions
Default value expressions can be used to return a default value for a given type:
int numeric = default(int); // = 0
Object reference = default(Object); // = null
DateTime value = default(DateTime); // = new DateTime()
They are especially useful in combination with generic types when we don’t know in advance what the default value for the given type will be:
bool IsDefault<T>(T value)
{
T defaultValue = default(T);
if (defaultValue != null)
{
return defaultValue.Equals(value);
}
else
{
return value == null;
}
}
C# 7.1 adds support for default literal expression, which can be used instead of default value expression whenever the type can be inferred from the context:
int numeric = default;
Object reference = default;
DateTime value = default;
T defaultValue = default;
The new default literal expression is not only useful in variable assignment, it can be used in other situations as well:
- in a return statement,
- as the default value for optional parameters,
- as the argument value when calling a method.
The literal expression syntax is equivalent to the value expression syntax but is terser, especially with long type names.
Inferred Tuple Element Names
Tuples were first introduced in C# 7.0. C# 7.1 is adding only a minor improvement to its behavior. When creating a tuple in C#, element names had to be explicitly given or the elements could only be accessed via default names Item1, Item2 etc.:
var coords1 = (x: x, y: y);
var x1 = coords1.x;
var coords2 = (x, y);
var x2 = coords2.Item1; // coords2.x didn't compile
In C# 7.1, tuple names can be inferred from the names of variables used to construct the tuple. Hence, the following code now compiles and works as expected:
var coords2 = (x, y);
var x2 = coords2.x;
Generic Pattern Matching
One of the most important new features in C# 7.0 was pattern matching using the is keyword and the switch statement. The type pattern allowed us to branch based on the value type:
void Attack(IWeapon weapon, IEnemy enemy)
{
switch (weapon)
{
case Sword sword:
// process sword attack
break;
case Bow bow:
// process bow attack
break;
}
}
However, this didn’t work for generically typed values. For example, the following code didn’t compile in C# 7.0:
void Attack<T>(T weapon, IEnemy enemy) where T : IWeapon
{
switch (weapon)
{
case Sword sword:
// process sword attack
break;
case Bow bow:
// process bow attack
break;
}
}
C# 7.1 extends type patterns to also support generic types, making the code above valid.
C# 7.2 – New Expected Features
The language development didn’t stop with the release of C# 7.1. The team is already working on the next minor version – 7.2. The release date is not yet announced and the new features cannot be tried out easily, although all the specifications and discussions around them are public.
As the release approaches, we can expect that the updated compiler supporting the new features will be included in Visual Studio 2017 Preview, which can safely be installed alongside the current Visual Studio 2017 release.
Several new language features are currently planned for C# 7.2, however they are still subject to change. Some of them could be postponed to a later version and new features could potentially be added as well.
Digital Separator after Base Specifier
In C# 7.0, separators were allowed to be used inside numeric literals to increase readability:
var dec = 1_000_000;
var hex = 0xff_ff_ff;
var bin = 0b0000_1111;
Additionally, C# 7.2 is planned to allow separators after the base specifier:
var hex = 0x_ff_ff_ff;
var bin = 0b_0000_1111;
Non-trailing Named Arguments
Named arguments were added to C# in version 4. They were primarily the tool to allow optional arguments: some parameters could be skipped when calling a method, but for all the parameters following it, the arguments had to be named so that the compiler could match them:
void WriteText(string text, bool bold = false, bool centered = false)
{
// method implementation
}
// method call
WriteText("Hello world", centered: true);
If the parameters are not optional, arguments can still be named to improve code readability and you can even change the order of arguments if you can’t remember what it is:
WriteText("Hello world", true, true); // difficult to understand
WriteText("Hello world", bold: true, centered: true); // better
WriteText("Hello world", centered: true, bold: true); // different order
However, C# doesn’t yet allow positional arguments to follow named arguments in the same method call:
WriteText("Hello world", bold: true, true); // not allowed
According to the current plans, this will become a valid method call in C# 7.2. Positional arguments will be allowed even if they follow a named argument, as long as all the named arguments are still in their correct position and the names are only used for code clarification purposes.
Private Protected
Common Language Runtime (CLR) supports a class member accessibility level that has no equivalent in the C# language and thus cannot be used: a protectedAndInternal member can be accessed from a subclass, but only if the subclass is within the same assembly as the base class declaring the member.
In C#, the base class developer must currently choose between two access modifiers that don’t match this behavior exactly:
- protected will make the member visible only to subclasses, but they could be in any assembly. There will be no restriction that they have to be placed in the same assembly.
- internal will restrict the visibility of the member to the same assembly, but all classes in that assembly will be able to access it, not only the subclasses of the base class declaring it.
For C# 7.2, a new access modifier is planned: private protected will match the protectedAndInternal accessibility level – members will only be visible to subclasses in the same assembly. This will prove useful to library developers who will not need to choose between exposing protected members outside the library and making internal members available to all classes inside their library.
Conditional Ref Operator
In C# 7.0, support for return values and local variables by reference was introduced. You can learn more about it from my previous article on C# 7.0 in the Dot Net Curry (DNC) magazine.
However, there is currently no way to conditionally bind a variable by reference to a different expression, similar to what the ternary or the conditional operator does when binding by value:
var max = a > b ? a : b;
Since variable bound by reference cannot be rebound to a different expression, this limitation cannot be worked around with an if statement:
ref var max = ref b; // requires initialization
if (a > b)
{
r = ref a; // not allowed in C# 7.1
}
For some cases, the following method could work as a replacement:
ref T BindConditionally<T>(bool condition, ref T trueExpression, ref T falseExpression)
{
if (condition)
{
return ref trueExpression;
}
else
{
return ref falseExpression;
}
}
// method call
ref var max = ref BindConditionally(a > b, ref a, ref b);
It will however fail if one of the arguments cannot be evaluated when the method is called:
ref var firstItem = ref BindConditionally(emptyArray.Length > 0, ref emptyArray[0], ref nonEmptyArray[0]);
This will throw an IndexOutOfRangeException because emptyArray[0] will still be evaluated.
With the conditional ref operator that’s planned for C# 7.2, the described behavior could be achieved. Just like with the existing conditional operator, only the selected alternative would be evaluated:
ref var firstItem = ref (emptyArray.Length > 0 ? ref emptyArray[0] : ref nonEmptyArray[0]);
Ref Local Reassignment
There is another extension of local variables and parameters bound by reference planned for C# 7.2 – the ability to rebind them to a different expression. With this change, the workaround for missing conditional ref operator from the previous section would work as well:
ref var max = ref b;
if (a > b)
{
r = ref a;
}
Read-only Ref
In performance sensitive applications, structs are often passed by reference to the called function, not because it should be able to modify the values, but to avoid copying of values. There is no way to express that in C# currently, therefore the intention can only be explained in documentation or code comments, which is purely informal and without assurance.
To address this issue, C# 7.2 is planned to include support for read-only parameters passed by reference:
static Vector3 Normalize(ref readonly Vector3 value)
{
// returns a new unit vector from the specified vector
// signature ensures that input vector cannot be modified
}
The syntax is not yet finalized. Instead of ref readonly, in could be used. Even both syntaxes might be allowed.
Blittable Types
There is a concept of unmanaged or blittable types in the Common Language Runtime, which have the same representation in managed and unmanaged memory.
This allows them to be passed between managed and unmanaged code without a conversion, making them more performant and thus very important in interoperability scenarios.
In C#, structs are currently implicitly blittable if they are composed only of blittable basic types (numerical types and pointers) and other blittable structs. Since there is no way to explicitly mark them as blittable, there is no compile time protection from unintentional changes to these structs, which would make them non-blittable.
Such changes can have a very large impact as any other struct including a struct that became non-blittable, will become non-blittable as well. This can break consumers without the developer being aware of it.
There is a plan for C# 7.2 to add an explicit declaration for blittable structs:
blittable struct Point
{
public int X;
public int Y;
}
The requirements for a struct to be declared as blittable would remain unchanged. However, such a struct would not automatically be considered blittable. To make it blittable, it would have to be explicitly marked with the blittable keyword.
With this change, the compiler could warn the developer when a change to the struct would make it non-blittable, while it was still declared as blittable. It would also allow blittable keyword to be used as a constraint for generic types, allowing the implementation of generic helper functions, which require their arguments to be blittable.
C# 8 – What’s Upcoming
In parallel to the development of the next minor language version, work is also being done on the next major version. All currently planned features are large in scope and impact. They are still in an early prototype phase and likely far away from release.
Nullable Reference Types
This feature was already considered in the early stages of C# 7.0 development, but was postponed until the next major version. Its goal is to help developers avoid unhandled NullReferenceExceptions.
The core idea is to allow variable type definitions to contain information, whether they can have a null assigned to them or not:
IWeapon? canBeNull;
IWeapon cantBeNull;
Assigning a null value or a potential null value to a non-nullable variable would result in a compiler warning (the developer could configure the build to fail in case of such warnings, to be extra safe):
canBeNull = null; // no warning
cantBeNull = null; // warning
cantBeNull = canBeNull; // warning
The problem with such a change is that it breaks existing code: it is assumed that all variables from before the change are non-nullable. To cope with that, static analysis for null safety could be disabled at the project level, as well as at the level of a referenced assembly.
The developer could opt-in to the nullability checking when he is ready to deal with the resulting warnings. Still, this would be in her/his own best interest, as the warnings might reveal potential bugs in his code.
Recursive Patterns
First pattern matching features have been added to C# in version 7.0. There are plans to further extend the support in C# 8.0.
Recursive patterns are one of the planned additions. They would allow parts of data to be matched against sub-patterns.
The proposal lists a symbolic expression simplifier as an example that could be implemented using this feature. It would require support for recursive types as means for representing the expressions:
abstract class Expr;
class X() : Expr;
class Const(double Value) : Expr;
class Add(Expr Left, Expr Right) : Expr;
class Mult(Expr Left, Expr Right) : Expr;
class Neg(Expr Value) : Expr;
Simplification could then be implemented as a recursive function, heavily relying on pattern matching:
Expr Simplify(Expr e)
{
switch (e) {
case Mult(Const(0), _): return Const(0);
case Mult(_, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
default: return e;
}
}
Default Interface Methods
Interfaces in C# are currently not allowed to contain method implementations. They are restricted to method declarations:
interface ISample
{
void M1(); // allowed
void M2() => Console.WriteLine("ISample.M2"); // not allowed
}
To achieve similar functionality, abstract classes can be used instead:
abstract class SampleBase
{
public abstract void M1();
public void M2() => Console.WriteLine("SampleBase.M2");
}
In spite of that, there are plans to add support for default interface methods to C# 8.0, i.e. method implementations using the syntax suggested in the first example above. This would allow scenarios not supported by abstract classes.
A library author could extend an existing interface with a default interface method implementation, instead of with a method declaration.
This would have the benefit of not breaking existing classes, which implemented the old version of the interface. If they didn’t implement the new method, they could still use the default interface method implementation. When they wanted to change that behavior, they could override it, but no code change would be required just because the interface was extended.
Since multiple inheritance is not allowed, a class can only derive from a single base abstract class.
In contrast to that limitation, a class can implement multiple interfaces. If these interfaces implement default interface methods, this effectively allows classes to compose behavior from multiple different interfaces – the concept is known as trait and is already available in many programming languages.
Unlike multiple inheritance, it avoids the so called diamond problem of ambiguity when a method with the same name is defined in multiple interfaces. To achieve that, C# 8.0 will require each class and interface to have a most specific override for each inherited member.
When a member with the same name is inherited from multiple interfaces, one override is more specific than the other when its interface is derived from the other one. When neither interface directly or indirectly inherits from the other interface, the developer will need to specify the override he wants to use or write his own override.
By doing so, he will explicitly resolve the ambiguity.
Conclusion:
C# compiler is delivering on the promise of Roslyn: faster introduction of new features thanks to a completely new codebase.
At the same time, new features are not forced onto larger teams who prefer to have stricter control over the language version they are using. They can evaluate new features and decide at their own pace when they want to adopt them.
In accordance to the open source model, even the state of upcoming features in future versions of the C# language is public and available for all to explore or even contribute their opinion to. It’s important to keep in mind though, that these features are still work in progress and as such they could change without warning or even be postponed to a later version.
The article was technically reviewed by Yacoub Massad.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Damir Arh has many years of experience with software development and maintenance; from complex enterprise software projects to modern consumer-oriented mobile applications. Although he has worked with a wide spectrum of different languages, his favorite language remains C#. In his drive towards better development processes, he is a proponent of Test-driven development, Continuous Integration, and Continuous Deployment. He shares his knowledge by speaking at local user groups and conferences, blogging, and writing articles. He is an awarded Microsoft MVP for .NET since 2012.