We are already used to the fact that a new version of Visual Studio also includes a new version of C#. Visual Studio 2015 is no exception to this: its new C# compiler supports version 6 of the language. Unlike previous language updates, this version is not focused on a single major feature. Instead, it includes several smaller improvements that did not make it into previous releases. Most of these features make the language more concise and less error prone.
Also Read: What's New in C# 7
However, does it make sense to change your existing C# code base to take advantage of the new C# 6.0 features? Let us find out.
This article is published from the DNC Magazine for .NET Developers and Architects. Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions
What Has Changed in C# 6.0?
Changes in C# 6.0 make the language less verbose. Most of them affect declarations of class members and building of expressions. The only exceptions are improvements to try-catch-finally statements and the introduction of using static clause. Filip Ekberg already wrote an article about them when Visual Studio 2015 was still in preview, but a lot has changed between then and the final release. Instead of just writing an updated article on new language features, I will rather focus on the benefits that they can bring to the code base, when used appropriately.
Best Features of C# 6.0
I will start out with new features, which you should start using in your code base immediately, wherever applicable, because they can have a positive impact on the code maintainability and readability. Some of these new features are: nameof operator, null-conditional operator, and support for await in catch and finally blocks. I would strongly suggest you apply them even to your existing code once you have switched to Visual Studio 2015.
Nameof Operator
Base class libraries sometimes require you to put symbol names (such as property and parameter names) as string literals in your code, e.g. when your class implements INotifyPropertyChanged interface or when throwing an ArgumentException:
public int GetFibonacciNumber(int n)
{
if (n < 0)
{
throw new ArgumentException("Negative value not allowed", "n");
}
// TODO: calculate Fibonacci number
return n;
}
When you rename the parameter, you will need to remember to change the string literal as well – the compiler will not warn you about it.
The nameof operator in C# 6.0 is a perfect solution to this problem: nameof(n) call will compile into a string literal while keeping strong typing at the source code level:
public int GetFibonacciNumber(int n)
{
if (n < 0)
{
throw new ArgumentException("Negative value not allowed", nameof(n));
}
// TODO: calculate Fibonacci number
return n;
}
Not only will the compiler now warn you if you forget to rename the parameter usage in nameof operator, rename refactoring in Visual Studio will automatically rename it for you.
Null-conditional Operator
Whenever you want to invoke a property or a method on a class, you need to check that the instance is not null beforehand, otherwise the dreaded NullReferenceException will be thrown at run time. This can result in a lot of trivial code, which exists only to check for values not being null:
public int GetStringLength(string arg)
{
return arg != null ? arg.Length : 0;
}
Null-conditional operator can replace all such null checking code:
public int GetStringLength(string arg)
{
return arg?.Length ?? 0;
}
The arg?.Length in the above code will return arg.Length when arg is not null ; otherwise, it will return null. You can even cascade multiple calls one after another: arg?.Length?.ToString() will return null if arg or arg?.Length are null and will never throw an exception.
Null-conditional operator is not limited to properties and methods. It also works with delegates, but instead of invoking the delegate directly, you will need to call its Invoke method:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Implementing events this way also ensures that they are thread-safe, unlike their naïve implementation in previous versions of C#, as shown here:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In multithreaded scenarios, the above code could throw a NullReferenceException, if another thread removed the last remaining event handler; after the first thread checked the event for being null, but before it called the delegate. To avoid this, PropertyChanged needed to be stored into a local variable in previous versions of C#, before checking it for null. The null-conditional operator in C# 6.0 takes care of this automatically.
Support for Await in Catch and Finally Blocks
The async and await keywords, introduced in C# 5.0, made asynchronous programming much easier. Instead of creating a callback for each asynchronous method call, all the code could now be in a single async method, with the compiler creating the necessary plumbing instead of the developer. Unfortunately, await keyword was not supported in catch and finally blocks. This brought additional complexity to calling asynchronous methods from error handling blocks when they were also required to complete before continuing. Here's an example:
public async Task HandleAsync(AsyncResource resource, bool throwException)
{
Exception exceptionToLog = null;
try
{
await resource.OpenAsync(throwException);
}
catch (Exception exception)
{
exceptionToLog = exception;
}
if (exceptionToLog != null)
{
await resource.LogAsync(exceptionToLog);
}
}
Of course, if you wanted to rethrow the exception and keep the stack trace, it would get even more complicated. With C# 6.0, none of this is required any more – async methods can simply be awaited inside both catch and finally blocks:
public async Task HandleAsync(AsyncResource resource, bool throwException)
{
try
{
await resource.OpenAsync(throwException);
}
catch (Exception exception)
{
await resource.LogAsync(exception);
}
finally
{
await resource.CloseAsync();
}
}
The compiler will do all the necessary plumbing itself, and you can be sure that it will work correctly.
Recommended C# 6.0 Features for New Projects
Some additional new features in C # 6.0 can simplify writing new code, but do not add enough benefits to justify modifying existing code. All of them are just syntactic sugar, which will save you some typing but will not make much difference otherwise.
String Interpolation
If C# is not the only programming language you work with, you might be familiar with this language construct called "String Interpolation" from other languages such as PowerShell, TypeScript, Python, etc. In most cases, you can use it instead of String.Format calls, making them easier to read and safer to use. Here is a simple example:
var formattedPrice = $"{price:0.00} €";
It is equivalent to the following String.Format call:
var formattedPrice = String.Format("{0:0.00} €", price);
Notice the following key aspects:
· Literals for string interpolation must start with $ character. This makes them of type FormattableString.
· You can directly use variable names and expressions in placeholders, instead of numerical values. They will be checked at compile time.
· Additional formatting strings can still be included in the placeholder after the colon, similar to String.Format.
Nevertheless, string interpolation is not a universal replacement for String.Format, e.g. you will still need to use String.Format for all localized strings and the translators will still have to deal with numeric placeholders.
Improvements to Member Declarations
You can take advantage of several new language constructs in your member declarations:
· Auto-properties can now be initialized the same way as fields:
public string FullName { get; set; } = "Damir Arh";
· Read-only auto-properties are now supported as well. They create a readonly backing field. Their value can only be set with an auto-property initializer or inside the constructor:
public string FullName { get; } = "Damir Arh";
· Simple methods and read-only properties can have their body defined with a lambda-like expression:
public int Add(int a, int b) => a + b;
public string FullName => _name + " " + _surname;
Exception filters
The final feature in this category is new only in C# 6.0; both Visual Basic and F# have already supported it in previous versions. It can be used to filter the exception caught by a specific catch block not only by its type but by using any exception property:
try
{
// ...
}
catch (ArgumentException exception) when (exception.ParamName == "ignore")
{ }
Before exception filters were available in C# 6.0, you could write the same code in previous versions of C# in the following manner
try
{
// ...
}
catch (ArgumentException exception)
{
if (exception.ParamName != "ignore")
{
throw;
}
}
As you can see in the first example, the code using exception filters is easier to understand, and it keeps the stack untouched when the filter condition is not matched. Although catching and re-throwing the exception in the second example preserves the stack trace inside the exception, the top frame on the CLR stack will point to the place it was rethrown, instead to where it was originally thrown. This can make an important difference in the information available when analysing a crash dump resulting from such an exception.
The Exception filter feature can also be abused for unobtrusive logging:
try
{
// ...
}
catch (Exception exception) when (Log(exception))
{ }
This way the Log method can log all the exception details without affecting the CLR stack. Of course, it must return false to prevent the catch block from executing.
Remaining C# 6.0 Features
There are a couple of new language features in C# 6.0, which I have not mentioned yet. To be honest, in most cases I don’t recommend using them, because although they can make the code less verbose, they can also make it more difficult to understand, especially in larger teams and in projects with longer time span.
For the sake of completeness, I am nevertheless listing them here:
- Using static clause can be used to import static class members into scope, so that they do not need to be prefixed with the class name any more. With the exception of a few well-known static classes, such as Math and Console, this can make it difficult to know where the methods in the code come from. In the following example both System.Console and System.Math are statically imported, which makes WriteLine and Pow methods directly available inside the scope:
using static System.Console;
using static System.Math;
class Program
{
static void Main()
{
WriteLine(Pow(10, 2));
}
}
- Index initializers expand on collection initializers and allow initialization of indexed values within a single expression. They can be used with any type having an indexer, but for most types, the same can be achieved using collection initializers. In the example below, index initializers are used to initialize a dictionary:
var properties = new Dictionary<string, string>
{
["Name"] = "Damir",
["Surname"] = "Arh",
["Country"] = "Slovenia"
};
- Collection initializers are not limited to calling instance methods named Add anymore. Now extension methods with the same name can be called as well. The following example will work as long as there is an extension method named Add accepting a single parameter (Dictionary class does not have such a method). For a developer unaware of such an extension method, the initializer can be completely incomprehensible:
var typeNames = new Dictionary<Type, string>
{
typeof(string)
};
Before using any of the language constructs in this section, consider the following:
· Does this construct really make the code better?
· Is there no better way to achieve the same result?
How Can Visual Studio Help?
Compared to the previous releases, static code analysis tools are vastly improved in Visual Studio 2015. It introduces unified framework diagnostic analyzers, which are presented to the user the same way, whether they are built into Visual Studio or provided by third party extensions or NuGet packages.
Code fixes are a part of this framework and could prove useful in refactoring existing code to take advantage of new features in C# 6.0. The ones that are enabled for the project appear as information, warnings or errors in the code (depending on the configuration). When activated in the editor window by clicking on the lightbulb icon or by pressing Ctrl + ., the fix (i.e. the refactoring) can be applied only to a single occurrence, or to all occurrences in a document project or complete solution. This means that with an appropriate code fix, a specific new language feature could be applied to the whole solution in one single action.
Image 1: Visual Studio diagnostic analyzer in action
Unfortunately, there are no such code fixes built into Visual Studio. To my knowledge, even in the third party space, there is only a single code fix available that applies one of suggested refactorings explained in this article: “Use ‘nameof(n)’ expression instead.”, distributed as a part of Refactoring Essentials Visual Studio extension at https://visualstudiogallery.msdn.microsoft.com/68c1575b-e0bf-420d-a94b-1b0f4bcdcbcc .
This does not mean that you are on your own though. The renowned Visual Studio extension ReSharper by JetBrains has been updated for Visual Studio 2015 and C# 6.0 and in its latest release (9.2 at the time of writing) it includes so called inspections for many of the new language features.
· nameof operator,
· null-conditional operator,
· string interpolation,
· read-only auto-properties, and
· expression-bodied properties.
The suggested fixes are also displayed as warnings and can by default be applied via the lightbulb icon or by pressing Alt + Enter. There is no preview of the changes though.
Image 2: Applying ReSharper quick-fixes
Most of the fixes can be applied at the document, project or solution scope with a single action, as well. For those that can’t be applied, invoking code inspection for the whole solution will create a list of all issues found, which can be used for navigating to them in code and fixing them one by one. It is still much quicker and reliable than doing it completely by hand.
Image 3: ReSharper code inspection results for solution
Unlike Refactoring Essentials, ReSharper is not a free extension and you will need to pay for it if you want to use it beyond the 30-day trial period. It does offer a lot more than just the refactorings, mentioned here, though.
Conclusion:
C# 6.0 brings many small improvements to the language. The categorization of those into three different categories in this article is based solely on my opinion and experience. As such, it is very subjective and your opinion on individual new features may vary. Nevertheless, if all of your team members are using Visual Studio 2015 (which is a prerequisite for using C# 6.0 language features), I strongly suggest, you take a closer look at what is new and decide for yourself which features you want to use, and which not. Of course, I would be very glad if you base your final decision at least partly on my opinions expressed in this article.
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.