Introduction
Let us dive into something really latest in the world of .NET. Microsoft is about to release a new version of C# language - version 7.0. Let's learn all its new features so that it can be handy whenever we upgrade our existing projects/solution to Visual Studio 15.
Background
I was going through a wiki on C# language created by Jon Skeet (my most favourite developer on this planet) on stackoverflow.com website. Here is the link to the question which talks about version history of C# language.
I suddenly noticed that the accepted answer doesn't mention the C# language version 7 which gave me a realization that it is not yet updated as the language version is yet to come to RTM milestone. So, I thought this particular language version is still in nascent phases of development so it will be exciting to share the detail of all new upcoming features in this language version to avid blog readers. Hence, I incepted this article.
Prerequisites
A basic knowledge of C# language and Visual Studio (VS) IDE from Microsoft is desired while reading this article and for using the attached source code.
A Note for the Attached Code
You need the following software as prerequisites to run the attached code on your computer:
- Visual Studio 15 Preview 5 - The first and foremost thing to understand is that Visual Studio 15 is NOT Visual Studio 2015. Visual Studio 2015 is the last RTMed version of Visual Studio which is currently available in the market. Visual Studio 15 is the next Visual Studio version to be released in market. It will either be named (colloquial name) as Visual Studio 2016 or Visual Studio 2017 when it gets finally RTMed.
-
An active internet connection - This is needed to download the nuget packages referenced in the attached source code. Before creating a zip of the source code, I emptied the packages directory because of its size (> 200 MB). The moment you rebuild the solution, all the packages will get downloaded automatically if you have an active internet connection and then the sample code should run smoothly.
C# v7 features can work in VS 2015 also but that requires few configurations. So, the best way to deal with this is to get the preview build of VS 15 from here. At the time of this writing, the latest build is being named "Preview 5". Install VS 15 Preview 5 to get started.
Note: C# v7 is not yet in RTM state. So, this feature list might not be exhaustive and complete from the final release stand-point. As the C# compiler team continues to work really hard, we can see more and more features working in new preview releases. Keep a watch on this blog post as I will keep updating it as more and more features become visible and runnable on Visual Studio 15. For absolutely latest updates, you can check their GitHub page here.
Exploring the Code for New Features
Let's begin.
Feature # 1 - Binary Literals
Old Behaviour
Did not exist until C# v6.0
New Behaviour
We have all used literal constant values for various primitive types in C# language. Here is the code snippet from the attached source code which shows few literal constant declarations for some of the primitive types:
private static void ExistingPrimitiveTypeLiterals()
{
int employeeNumber = 34;
float passPercentage = 34.0f;
int employeeAge = 0x22;
}
Until C# v6.0, there was no way for us to represent a binary literal value in code. C# v7.0 solves this problem in the upcoming version. They have introduced a new prefix "0b" (or "0B") whenever you want to declare a binary literal in your code. The underlying data type of such a variable will still be integer (System.Int32
or System.Int64
) just like the way it was for hexdecimal numbers. These will be base 2 numerical numbers with 0 and 1 being only possible digits in such a representation. This is how you would be representing a binary literal in code:
private static void BinaryLiteralsFeature()
{
var employeeNumber = 0b00100010;
Console.WriteLine(employeeNumber);
long empNumberWithLongBackingType = 0b00100010;
Console.WriteLine(empNumberWithLongBackingType);
int employeeNumber_WithCapitalPrefix = 0B00100010;
Console.WriteLine(employeeNumber_WithCapitalPrefix);
}
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 2 - Digit Separators
Old Behaviour
Did not exist until C# v6.0
New Behaviour
Now you can improve readability of your long numeric literals by using underscore as digit separator. Use them anywhere and any number of times within the numeric literal as per your convenience. They will be especially useful in binary literals as they usually have long representations. Compiler simply ignores the underscores while compiling the code. Here is the code snippet from the attached source code which shows digit separators in action:
private static void DigitSeparatorsFeature()
{
int largeNaturalNumber = 345_2_45;
int largeNaturalNumber_oldFormat = 345245;
long largeWholeNumber = 1_2_3_4___5_6_7_8_0_10;
long largeWholeNumber_oldFormat = 12345678010;
int employeeNumber = 0b0010_0010;
}
There are few restrictions though while using digit separators:
- Underscore can't appear at the start of the number.
- Underscore can't appear before decimal point.
- Underscore can't appear after exponential sign.
- Underscore can't appear before type specifier suffix.
The following piece of code showcases these behaviors:
private static void DigitSeparatorsFeature()
{
int underscoreAtStarting = _23_34;
int underscoreBeforeDecimalPoint = 10_.0;
double underscoreAfterExponentialSign = 1.1e_1;
float underscoreBeforeTypeSpecifier = 34.0_f
}
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 3 - Tuple Data Type Now Available as Value Type
Old Behaviour
Tuple data type was introduced as a new data type in C# v4.0. Until C# v6.0, it was available only as a reference data type inside System
namespace. This is how you declare a generic Tuple variable in C# v6.0:
private static void TupleReferenceTypeLiterals()
{
Tuple<int, string, bool> tuple = new Tuple<int, string, bool>(1, "cat", true);
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
Console.WriteLine(tuple.Item3);
}
Returning multiple values from a function was the primary usage of Tuples
till date.
New Behaviour
Using Tuples will become a cakewalk from upcoming version. Tuples will now be available as a value type. Tuples rely on the underlying type System.ValueTuple
which is a struct
. This type isn’t included in Preview 4 build. So, if you are using Preview 4 or an older build, then you will have to add the underlying type via NuGet into your project using below mentioned steps:
- Right-click the project in the solution explorer and select “Manage NuGet Packages…”
- Select the “Browse” tab, check “Include prerelease” and select “nuget.org” as the “Package source”
- Search for “
System.ValueTuple
” and install it.
Here is how you define tuple literals in C# v7.0:
private static void TupleValueTypeLiterals()
{
var genders = ("Male", "Female");
Console.WriteLine("Possible genders in human race are : {0} and {1} ",
genders.Item1, genders.Item2);
Console.WriteLine($"Possible genders in human race are
{genders.Item1}, {genders.Item2}.");
var geoLocation = new(double latitude, double longitude)
{ latitude = 124, longitude = 23 };
Console.WriteLine("Geographical location is : {0} , {1} ",
geoLocation.longitude, geoLocation.latitude);
var employeeDetail = ("Michael", 33, true);
Console.WriteLine("Details of employee are Name: {0}, Age : {1},
IsPermanent: {2} ", employeeDetail.Item1, employeeDetail.Item2, employeeDetail.Item3);
var employeeRecord = new(Employee emp, int Id)
{ emp = new Employee { FirstName = "Foo", LastName = "Bar" }, Id = 1 };
Console.WriteLine("Employee details are - Id: {0}, First Name: {1},
Last Name: {2}", employeeRecord.Id, employeeRecord.emp.FirstName,
employeeRecord.emp.LastName);
}
Tuples will also be a real boon for returning multiple values from a function. There had already been numerous ways in the language to return multiple values from a function but none of them had been as transparent and crisp as this newly introduced Tuples. So far, you could have been using the following ways to return multiple values from a function:
- returning an array list
- Define a
struct
and return it - Define a class with a
public
property each for the values to be returned out
parameters - returning a
Tuple<T1,T2...Tn>
type
What would be simpler than the one below. Have a look!
static void Main(string[] args)
{
var geographicalCoordinates = ReturnMultipleValuesFromAFunction("London");
Console.WriteLine(geographicalCoordinates.longitude);
Console.WriteLine(geographicalCoordinates.Item1);
Console.WriteLine(geographicalCoordinates.latitude);
Console.WriteLine(geographicalCoordinates.Item2);
}
private static (double longitude,double latitude)
ReturnMultipleValuesFromAFunction(string nameOfPlace)
{
var geoLocation = (0D, 0D);
switch (nameOfPlace)
{
case "London":
geoLocation = (24.9277, 84.1910);
break;
default:
break;
}
return geoLocation;
}
Few very straight forward visible advantages of new Tuple
type are as below:
- You don't have to create explicit type. Types are created on the fly as anonymous by the compiler.
- Now you can name the
public
members. In System.Tuple<T>
compiler used to give generic names, e.g., Item1
, Item2
, Item3
, etc. to public
members of the tuple. - Internally, the type is a
struct
so gets allocated on stack memory area. So, no heavy heap object allocation.
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 4 - Out Parameters Now Have a Fluid Usage
Old Behaviour
If you have ever used out
parameter to get a return value from a function, then you would remember the ugly way of declaring a variable before passing it to the out
parameter of the function. Have a look:
private static void OutParameterOldUsage()
{
var number = "20";
int parsedValue;
int.TryParse(number, out parsedValue);
}
New Behaviour
Now that has become really sweet and simple. The variable to be passed as the out
parameter of the function call can be declared in-line with the function call itself as shown in the code snippet below:
private static void OutParameterNewUsage()
{
var number = "20";
if (int.TryParse(number, out int parsedValue))
Console.WriteLine(parsedValue);
else
Console.WriteLine("The input number was not in correct format!");
if (int.TryParse(number, out var parsedValue2))
Console.WriteLine(parsedValue2);
else
Console.WriteLine("The input number was not in correct format!");
}
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 5 - Local Functions
Old Behaviour
Did not exist until C# v6.0
New Behaviour
This, by far, had been in the news the most since the proposed feature list was getting prepared for the new version of C# language. We often write functions representing complex and long algorithmic implementation. We often break the function into several multiple functions for clear understanding and easy maintenance. When we do so most of the times, such smaller functions will not be reusable by other parts of your program/class as they are not generic enough and they are a specific step of your algorithmic problem you are solving currently.
Such functions which have got single place usage can now be defined as local functions so that they don't clutter unnecessarily and cause confusion. They are nested inside the function where they are to be called.
private static void LocalFunctionsFeature()
{
string GetAgeGroup(int age)
{
if (age < 10)
return "Child";
else if (age < 50)
return "Adult";
else
return "Old";
}
Console.WriteLine("My age group is {0}", GetAgeGroup(33));
}
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 6 - Ref Returns and Ref Locals
Old Behaviour
If you haven't been even a year older in .NET world, then you must have used the ref
keyword. In C#, by default, parameters are passed by value, i.e., a copy of the value is passed instead of the value itself. On similar lines, another mechanism to pass parameters to a function is "Pass by Reference". In this case, the actual value is passed instead of its copy. So, if you modify the argument (which has been passed by reference) inside the method body, the corresponding argument present in the caller function will also get impacted. Have a look at the below code snippet to understand it:
static void Main(string[] args)
{
Console.WriteLine("******Passing arguments by reference*********");
string empName = "Foo";
UpdateEmployeeName(ref empName);
Console.WriteLine(empName);
}
private static void UpdateEmployeeName(ref string empName)
{
empName = "Bar";
}
So, until C# v6.0, it was possible to use ref
keyword only with input parameters of a function.
New Behaviour
Well, you might want to look at this SO thread and can see how someone's curiosity has actually turned out to be a feature in C# after five long years. Now from this new version of C#, we can use ref
keyword even with return value of the function. I noticed one thing. This feature is not an exact complement of passing values by reference into a function. It is not the case that you can simply declare a local variable inside your function and return its reference back to the caller. That's just not possible. As you know, all the variables local to a function are allocated on stack. Stack frames and its corresponding memory allocation are trashed right after the function call finishes. So, your local variables cannot outlive the execution life time of a function.
Let's try to see how it can really be put to use in code. Ultimately, what you can achieve is passing back the reference of a parameter which was passed by reference:
static void Main(string[] args)
{
Console.WriteLine("******ref locals and ref returns*********");
int x = 10, y = 20;
ref var newEmpNumber = ref GetBiggerNumber(ref x, ref y);
newEmpNumber += 20;
Console.WriteLine(y);
}
private static ref int GetBiggerNumber(ref int num1, ref int num2)
{
ref int localVariable = ref num1;
if (num1 > num2)
return ref num1;
else
return ref num2;
}
Here, localVariable
is a ref local variable. I have showed it here just for showing the declaration syntax of ref local variables. It was not really needed for the logic of my function.
Honestly speaking, I'm myself am not sure currently about the use case where I will this feature to use. As far as I am reading at several places, I can see this feature is more to pacify the needs of developers trying to port their code from C/C++.
Current State in Visual Studio 15 Preview 5 Build
Working
Feature # 7 - New Possibility for Throw Expressions
Old Behaviour
We have all used throw expressions whenever we want to take catch
an exception, take some intermediate action and then throw the exception again to keep the error bubbling back to the caller or original call site in the call stack. Let's see this quickly how it works currently:
private static void SimpleThrowImplementation()
{
int zero = 0;
try
{
var result = 1 / zero;
}
catch (Exception ex)
{
throw new ArithmeticException("Divide by zero exception
occurred in SimpleThrowApplication method");
}
}
Until C# v6.0, a throw
statement is always a stand-alone statement. It was not possible to club it with any other statement or expression.
New Behaviour
Now in C# v7.0, you can also use the throw
statement in the middle of an expression, e.g., a conditional ternary operator as shown in the code below:
private static int ThrowUsageInAnExpression(int value = 40)
{
return value < 20 ? value : throw new ArgumentOutOfRangeException
("Argument value must be less than 20");
}
Current State in Visual Studio 15 Preview 5 Build
Not working
Feature # 8 - New Possibility for Lambda Expression Bodied Methods
Old Behaviour
C# v6.0 had introduced expression bodied methods where you can define the body of the method as a lambda expression as shown in the code snippet below:
public class Employee
{
private string _firstName;
private string _lastName;
public string GetFirstName() => _firstName;
}
But this usage had a lot of limitations as the same construct was not allowed while defining properties, constructors, finalizers, etc.
New Behaviour
Beginning C# v7.0, it is planned to allow expression bodied definition for properties, constructors, finalizers, etc. also as shown in the code snippet below:
public class Employee
{
private string _firstName;
private string _lastName;
public string FirstName
{
get => _firstName;
set => _firstName = value;
}
}
Current State in Visual Studio 15 Preview 5 Build
Not working
Feature # 9 - Pattern Matching for Types
Old Behaviour
Did not exist until C# v6.0. Please do not confuse this feature with regular expression pattern matching. You all must have used System.Text.RegularExpressions.Regex
class for doing string based pattern matching in C#, e.g., to validate an email id being sent as input from the user. This new feature of pattern matching has nothing to do with string based pattern matching. This is a completely new paradigm which has to do with matching of classes and types in C#.
New Behaviour
Now pattern matching has been introduced for types also. These have been categorized as three types:
- Constant patterns, e.g.,
null
, int.MaxValue
- Type patterns, e.g.,
int i
- Var patterns, e.g.,
var v
These are new concepts so I believe it will be easier to understand them by examples. Let us see all the above pattern types in action through is
operator in C#. Here is the code snippet:
static void Main(string[] args)
{
Console.WriteLine("******Pattern matching with Types*********");
PatternMatchingFeatureWithTypes(2147483647);
PatternMatchingFeatureWithTypes(null);
PatternMatchingFeatureWithTypes(5D);
PatternMatchingFeatureWithTypes(5);
}
public static void PatternMatchingFeatureWithTypes(object input)
{
if (input is int.MaxValue)
{
return;
}
if (input is null)
return;
if (!(input is int i))
return;
Console.WriteLine(new string('*', i));
if (input is var v)
{
Console.WriteLine("var pattern will always have a match.");
}
}
Let us see constant pattern and type pattern in action through switch-case
expression in C#. You will also see a new keyword when
getting used. Here is the code snippet:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("******Type Pattern matching in switch-case*********");
var circleShape = new Circle();
circleShape.Radius = 5;
PatternMatchingFeatureWithSwitchCase(circleShape);
var squareShape = new Rectangle();
squareShape.Length = 10;
squareShape.Width = 10;
PatternMatchingFeatureWithSwitchCase(circleShape);
}
public static void PatternMatchingFeatureWithSwitchCase(object input)
{
var shape = input as Shape;
if (shape != null)
{
switch (shape)
{
case Circle c:
Console.WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Width):
Console.WriteLine($"{s.Length} x {s.Width} square");
break;
case Rectangle r:
Console.WriteLine($"{r.Length} x {r.Width} rectangle");
break;
default:
Console.WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
}
}
}
class Shape
{
}
class Circle : Shape
{
public int Radius
{
get;
set;
}
}
class Rectangle : Shape
{
public int Length
{
get;
set;
}
public int Width
{
get;
set;
}
}
Current State in Visual Studio 15 Preview 5 Build
Working
Points of Interest
Please comment if I've missed any of the features in case it is actually planned for the upcoming release but I've not included in my blog. For your interest, you can explore more on one such feature which I'm still exploring and trying to understand:
- Generalized async return types
History
- 16th November, 2016: First draft
- 22nd November, 2016: Fixed formatting and grammar at several places
- 25th November, 2016: Changed article tags for Visual Studio
- 9th December, 2016: Fixed the title of feature # 4
- 13th December, 2016: Fixed error in feature # 1 based on reader's feedback
- 17th December, 2016: Fixed error in code snippet of feature # 8 based on reader's feedback
- 3rd January, 2017: Fixed typographical error in code snippet of feature # 4 based on reader's feedback