Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# Version 7 - Introduction to New Language Features

4.92/5 (97 votes)
2 Jan 2017CPOL12 min read 100.3K   441  
Let's get our hands dirty with new features of upcoming version 7 of C# language in .NET Framework

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:

  1. 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.
  2. 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:

C#
private static void ExistingPrimitiveTypeLiterals()
{
    int employeeNumber = 34;      // integer literal constant
    float passPercentage = 34.0f; // float literal constant
    int employeeAge = 0x22;       // hexadecimal literal which is equivalent of 
                                  // decimal number 34
}

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:

C#
private static void BinaryLiteralsFeature()
{
    var employeeNumber = 0b00100010;   // binary equivalent of whole number 34. 
                                       // Underlying data type defaults to System.Int32
    Console.WriteLine(employeeNumber); // prints 34 on console.
    long empNumberWithLongBackingType = 0b00100010;      // here backing data type is long 
                                                         // (System.Int64)
    Console.WriteLine(empNumberWithLongBackingType);     // prints 34 on console.
    int employeeNumber_WithCapitalPrefix = 0B00100010;   // 0b and 0B prefixes are equivalent.
    Console.WriteLine(employeeNumber_WithCapitalPrefix); // prints 34 on console.
}

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:

C#
private static void DigitSeparatorsFeature()
{
    // underscores coming in between the numbers have no impact 
    // on the underlying value being stored
    // it is simply to improve readability for long literal numerical values.
    int largeNaturalNumber = 345_2_45;
    int largeNaturalNumber_oldFormat = 345245;     // same as above
    long largeWholeNumber = 1_2_3_4___5_6_7_8_0_10;
    long largeWholeNumber_oldFormat = 12345678010; // same as above
    int employeeNumber = 0b0010_0010;              // specially useful in binary equivalents
}

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:

C#
private static void DigitSeparatorsFeature()
{
    int underscoreAtStarting = _23_34;             // illegal! Will not compile
    int underscoreBeforeDecimalPoint = 10_.0;      // illegal! Will not compile
    double underscoreAfterExponentialSign = 1.1e_1;// illegal! Will not compile
    float underscoreBeforeTypeSpecifier = 34.0_f   // illegal! Will not compile
}

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:

C#
private static void TupleReferenceTypeLiterals()
{
    Tuple<int, string, bool> tuple = new Tuple<int, string, bool>(1, "cat", true);
    /*Console prints
     1
     cat
     true
     */
    Console.WriteLine(tuple.Item1); // compile creates public properties 
                                    // using a "Item" series based naming convention
    Console.WriteLine(tuple.Item2); // Item1, Item2 and Item3 properties start appearing 
                                    // in intellisense magically
    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:

C#
private static void TupleValueTypeLiterals()
{
    // types of struct members are automatically inferred by the compiler as string, string
    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}.");

    // to override the default named Tuple public properties Item1 and Item2
    var geoLocation = new(double latitude, double longitude) 
                      { latitude = 124, longitude = 23 };
    Console.WriteLine("Geographical location is : {0} , {1} ", 
                       geoLocation.longitude, geoLocation.latitude);

    // hetrogeneous data types are also possible in tuple
    var employeeDetail = ("Michael", 33, true); //(string,int,bool)
    Console.WriteLine("Details of employee are Name: {0}, Age : {1}, 
    IsPermanent: {2} ", employeeDetail.Item1, employeeDetail.Item2, employeeDetail.Item3);

    // a reference type object can also be present as tuple member
    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!

C#
static void Main(string[] args)
{
     // Some code has been removed for brevity. 
     // Please download attached code for complete reference.
     var geographicalCoordinates = ReturnMultipleValuesFromAFunction("London");
     Console.WriteLine(geographicalCoordinates.longitude);
     Console.WriteLine(geographicalCoordinates.Item1); //prints same as longitude
     Console.WriteLine(geographicalCoordinates.latitude);
     Console.WriteLine(geographicalCoordinates.Item2); //print same as latitude
}

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:

  1. You don't have to create explicit type. Types are created on the fly as anonymous by the compiler.
  2. 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.
  3. 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:

C#
private static void OutParameterOldUsage()
{
   var number = "20";
   int parsedValue; // pre-declare the variable to be passed as out argument
   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:

C#
private static void OutParameterNewUsage()
{
    var number = "20";
    // pre-declaration for out parameter variable NOT required anymore
    // int parsedValue;

    // inline declaration for out parameter variable.
    if (int.TryParse(number, out int parsedValue))
        Console.WriteLine(parsedValue);
    else
        Console.WriteLine("The input number was not in correct format!");

    // even var keyword would do
    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.

C#
private static void LocalFunctionsFeature()
{
    // This function is not visible to any other function in this classs.
    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:

C#
static void Main(string[] args)
{
    // Some code has been removed for brevity. 
    // Please download the attached code for complete reference
    Console.WriteLine("******Passing arguments by reference*********");
    string empName = "Foo";
    UpdateEmployeeName(ref empName);
    Console.WriteLine(empName);//prints Bar
}

private static void  UpdateEmployeeName(ref string empName)
{
    // This change will impact the variable in caller function which has
    // been passed by reference.
    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:

C#
static void Main(string[] args)
{
    // Some code has been removed for brevity. 
    // Please download the attached code for complete reference
    Console.WriteLine("******ref locals and ref returns*********");
    int x = 10, y = 20;
    // After completion of the function, newEmpNumber is actually an alias to variable y
    ref var newEmpNumber = ref GetBiggerNumber(ref x, ref y);
    // Now I'm actually incrementing y by below statement through newEmpNumber 
    // which is referring to y only
    newEmpNumber += 20;
    Console.WriteLine(y); //in reality y got incremented to 20 + 20 = 40
}

private static ref int GetBiggerNumber(ref int num1, ref int num2)
{
    // How to declare a ref local variable. 
    // Not really needed for the logic of this function
    ref int localVariable = ref num1; 
    // You are not returning a value but a reference.
    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:

C#
private static void SimpleThrowImplementation()
{
    int zero = 0;
    try
    {
        var result = 1 / zero;
    }
    catch (Exception ex)
    {
        // log the arithmetic exception in log file or event viewer
        //....
        //....
        // now throw the exception for the caller function to catch it 
        // and do appropriate handling.
        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:

C#
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:

C#
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:

C#
public class Employee
{
    private string _firstName;
    private string _lastName;

    // Not working in Visual Studio 15 Preview 5 currently
    public string FirstName
    {
        get => _firstName;                                 // getters
        set => _firstName = value;                         // setters
    }
}

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:

  1. Constant patterns, e.g., null, int.MaxValue
  2. Type patterns, e.g., int i
  3. 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:

C#
static void Main(string[] args)
{
    // Some code has been removed for brevity. 
    // Please download the attached code for complete reference.
  
    Console.WriteLine("******Pattern matching with Types*********");
    // Prints nothing on console because of constant pattern matching 
    // inside the function call
    PatternMatchingFeatureWithTypes(2147483647);

    // Prints nothing on console because of constant pattern matching 
    // inside the function call
    PatternMatchingFeatureWithTypes(null);

    // Prints nothing on console because of type pattern mismatch inside the function call
    PatternMatchingFeatureWithTypes(5D);

    // Prints five asterisks on console because of type pattern matching 
    // inside the function call
    // Also prints a message on console because of var pattern matching 
    // inside the function call
    PatternMatchingFeatureWithTypes(5);
}

public static void PatternMatchingFeatureWithTypes(object input)
{
    // constant pattern "int.MaxValue"
    if (input is int.MaxValue)
    {
        return;
    }

    // constant pattern "null"
    if (input is null)
        return;

    // type pattern "int i"
    if (!(input is int i))
        return;

    // Since type pattern got satisfied above, variable i 
    // gets the value present in input parameter.
    Console.WriteLine(new string('*', i));

    // var pattern gets always satisfied for any type
    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:

C#
class Program
{
    static void Main(string[] args)
    {
        // Some code has been removed for brevity.
        // Please download the attached code for complete reference.
        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)
            {
                // use of type pattern
                case Circle c:
                    // if case gets satisfied, then c actually starts to act
                    // as a variable which will get assigned the value of input variable
                    // after appropriate casting.
                    Console.WriteLine($"circle with radius {c.Radius}");
                    break;
                case Rectangle s when (s.Length == s.Width): //when is a new keyword in C#
                    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: // use of constant null pattern
                    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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)