DotNetCurry Logo

C# 7 - What to Expect

Posted by: Damir Arh , on 9/25/2016, in Category C#
Views: 62105
Abstract: C# 7 is the next version of C#. This article provides an overview of the current state of thought process involved while designing new C# 7.0 features.

This article was updated on September 8th & 25th 2016.

The current incarnation of C# compiler (better known as Roslyn) was open sourced in April 2014. Not only is the compiler now being developed on GitHub; the language design is also openly discussed in public. This allows all interested parties to see how the next version of the language might look like. This article provides an overview of the current state of thought process involved while designing new features. If you are interested in a broader aspect of the current Roslyn ecosystem, you can read my article in the March 2016 edition of DotNetCurry (DNC) magazine: .NET Compiler Platform (a.k.a. Roslyn) – An Overview.

This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions

Main Themes for the Next Release of C#

New features in each version of C# so far (with the exception of C# 6.0 maybe) have revolved around a specific theme:

  • C# 2.0 introduced generics.
  • C# 3.0 enabled LINQ by bringing extension methods, lambda expressions, anonymous types and other related features.
  • C# 4.0 was all about interoperability with dynamic non-strongly typed languages.
  • C# 5.0 simplified asynchronous programming with the async and await keywords.
  • C# 6.0 had its compiler completely rewritten from scratch, and introduced a variety of small features and improvements that were easier to implement now. You can find an overview of all new C# 6.0 features in the Jan 2016 edition of the DotNetCurry (DNC) magazine: Upgrading Existing C# Code to C# 6.0.

Designing C# 7.0

C# 7.0 will probably be no exception to this rule. The language designers are currently focusing on three main themes:

  • Working with Data - Increased usage of web services is changing the way data is being modelled. Instead of designing the data models as a part of the application, their definition is becoming a part of web service contracts. While this is very convenient in functional languages, it can bring additional complexity to object oriented development. Several C# 7.0 features are targeted at making it easier to work with external data contracts.
  • Improved Performance - Increased share of mobile devices is making performance an important consideration again. There are planned features for C# 7.0 that could allow performance optimizations, which were previously not possible in the .NET framework.
  • Code simplification - Several additional small changes built on the work done for C# 6.0 to allow further simplification of the code written.

Let us take a closer look at some of the planned features belonging to each of these themes.

C# 7 Roadmap – Expected New Features

Working with Data

Object oriented languages like C# work great in scenarios with a predefined set of operations acting on an extensible set of data types. These are typically being modelled with an interface (or a base class) specifying the available operations and a potentially growing number of classes representing the data types. By implementing the interface, classes contain the implementation of all the expected operations.

For example, in a game, the classes could be different types of weapons (such as a sword or a bow), and the operations could be different actions (such as attack or repair). In this scenario, adding a new weapon type (e.g. a lightsaber) would be simple: just create a new class that implements the weapon interface. Adding a new action (e.g. attack) on the other hand would require extending the interface and modifying all the existing weapon implementations. This approach feels very natural in C#.

interface IEnemy
{
    int Health { get; set; }
}

interface IWeapon
{
    int Damage { get; set; }
    void Attack(IEnemy enemy);
    void Repair();
}

class Sword : IWeapon
{
    public int Damage { get; set; }
    public int Durability { get; set; }

    public void Attack(IEnemy enemy)
    {
        if (Durability > 0)
        {
            enemy.Health -= Damage;
            Durability--;
        }
    }

    public void Repair()
    {
        Durability += 100;
    }
}

class Bow : IWeapon
{
    public int Damage { get; set; }
    public int Arrows { get; set; }

    public void Attack(IEnemy enemy)
    {
        if (Arrows > 0)
        {
            enemy.Health -= Damage;
            Arrows--;
        }
    }

    public void Repair()
    { }
}

In functional programming, data types do not include operations. Instead, each function implements a single operation for all the data types. This makes it much easier to add a new operation (just define and implement a new function), but much more difficult to add a new data type (modify all existing functions accordingly). While this is already possible in C#, it is much more verbose than it could be.

interface IEnemy
{
    int Health { get; set; }
}

interface IWeapon
{
    int Damage { get; set; }
}

class Sword : IWeapon
{
    public int Damage { get; set; }
    public int Durability { get; set; }
}

class Bow : IWeapon
{
    public int Damage { get; set; }
    public int Arrows { get; set; }
}

static class WeaponOperations
{
    static void Attack(this IWeapon weapon, IEnemy enemy)
    {
        if (weapon is Sword)
        {
            var sword = weapon as Sword;
            if (sword.Durability > 0)
            {
                enemy.Health -= sword.Damage;
                sword.Durability--;
            }
        }
        else if (weapon is Bow)
        {
            var bow = weapon as Bow;
            if (bow.Arrows > 0)
            {
                enemy.Health -= bow.Damage;
                bow.Arrows--;
            }
        }
    }

    static void Repair(this IWeapon weapon)
    {
        if (weapon is Sword)
        {
            var sword = weapon as Sword;
            sword.Durability += 100;
        }
    }
}

Pattern matching is the feature that should help simplify the above code. Let us apply it to the Attack method in a step-by-step manner:

static void Attack(this IWeapon weapon, IEnemy enemy)
{
    if (weapon is Sword sword)
    {
        if (sword.Durability > 0)
        {
            enemy.Health -= sword.Damage;
            sword.Durability--;
        }
    }
    else if (weapon is Bow bow)
    {
        if (bow.Arrows > 0)
        {
            enemy.Health -= bow.Damage;
            bow.Arrows--;
        }
    }
}

Instead of checking the type of weapon and assigning it to a correctly typed variable in two separate statements, the is operator will now also allow us to declare a new variable and assign the type cast value to it.

With a similar result, a switch case statement can be used instead of if. This can make the code even more clear, especially when there are many different branches:

switch (weapon)
{
    case Sword sword when sword.Durability > 0:
        enemy.Health -= sword.Damage;
        sword.Durability--;
        break;
    case Bow bow when bow.Arrows > 0:
        enemy.Health -= bow.Damage;
        bow.Arrows--;
        break;
}

Notice, how the case statement performs both the type cast assignment, and the additional conditional check, increasing the terseness of the code.

There is another new feature that should contribute to making the code terser: tuples. They are supposed to be an even more lightweight alternative to anonymous classes. Their primary use will probably be in functions that return multiple values, as an alternative to out arguments:

public (int weight, int count) Stocktake(IEnumerable weapons) {     var w = 0;     var c = 0;     foreach (var weapon in weapons)     {         w += weapon.Weight;         c++;     }     return (w, c); }

The method above returns two separate values without using out arguments and without having to declare a new type just for this purpose. The syntax for calling the method remains familiar:

var inventory = Stocktake(weapons);
Debug.WriteLine($"Weapon count: {inventory.count}");
Debug.WriteLine($"Total weight: {inventory.weight}");

The return value is similar to the generic Tuple<> class from .NET framework. However, there are two important differences:

  • Its members can have meaningful names as defined in the called method and are not limited to non-descriptive Item1, Item2 etc. This makes the code much easier to read.
  • It is a value type instead of a reference type.

Alternatively, the new deconstruction language feature can be used when calling the method:

(var inventoryWeight, var inventoryCount) = Stocktake(weapons);
Debug.WriteLine($"Weapon count: {inventoryCount}");
Debug.WriteLine($"Total weight: {inventoryWeight}");

In this case, the caller specifies the names for individual members of the return value. This syntax will allow ignoring the members that the caller is not interested in by using wildcards:

(var inventoryWeight, *) = Stocktake(weapons);
Debug.WriteLine($"Total weight: {inventoryWeight}");

Deconstruction is not limited to tuples. Any type can support it as long as there is a deconstruction method defined for it, either as its member, or as an extension method:

public static void Deconstruct(this Sword sword, out int damage, out int durability)
{
    damage = sword.Damage;
    durability = sword.Durability;
}

The extension method above makes the following code valid:

var sword = new Sword { Damage = 5, Durability = 7 };
(var damage, var durability) = sword;
Debug.WriteLine($"Sword damage rating: {damage}");
Debug.WriteLine($"Sword durability: {durability}");

Improved Performance in C# 7

The performance improvements in C# 7.0 focuses on reducing the copying of data between memory locations.

Local functions will allow declaration of helper functions nested inside other functions. This will not only reduce their scope, but also allow the use of variables declared in their encompassing scope, without allocating additional memory on heap or stack:

static void ReduceMagicalEffects(this IWeapon weapon, int timePassed)
{
    double decayRate = CalculateDecayRate();
    double GetRemainingEffect(double currentEffect) => 
        currentEffect * Math.Pow(decayRate, timePassed);

    weapon.FireEffect = GetRemainingEffect(weapon.FireEffect);
    weapon.IceEffect = GetRemainingEffect(weapon.IceEffect);
    weapon.LightningEffect = GetRemainingEffect(weapon.LightningEffect);
}

Return values and local variables by reference can also be used to prevent unnecessary copying of data. At the same time, they modify the behavior. Since the variable is pointing at the original memory location, any changes to the value there will of course also affect the local variable value:

[Test]
public void LocalVariableByReference()
{
    var terrain = Terrain.Get();

    ref TerrainType terrainType = ref terrain.GetAt(4, 2);
    Assert.AreEqual(TerrainType.Grass, terrainType);

    // modify enum value at the original location
    terrain.BurnAt(4, 2);
    // local value was also affected
    Assert.AreEqual(TerrainType.Dirt, terrainType);
}

In the above example, terrainType is a local variable by reference, and GetAt is a function returning a value by reference:

public ref TerrainType GetAt(int x, int y) => ref terrain[x, y];

The final planned performance improvement in C# 7.0 targets asynchronous methods. Currently all asynchronous methods must return Task, which is a reference type. Now, a value type will be available as well: ValueTask. If the asynchronous method already has the result available when called, this can lead to significant performance improvements due to reduced number of allocations on the heap. Additionally, the language will now allow other custom return types for asynchronous methods, which might make sense for libraries with specific requirements.

Code Simplification in C# 7

C# 6.0 introduced support for expression bodied methods and read-only properties. Now, read/write properties, constructors and finalizers will also support this:

class Grunt : IEnemy
{
    public Grunt(int health) => _health = health >= 0 ? health : throw new ArgumentOutOfRangeException();
    ~Grunt() => Debug.WriteLine("Finalizer called");

    private int _health;
    public int Health
    {
        get => _health = 0;
        set => _health = value >= 0 ? value : 0;
    }
}

The class above takes advantage of another new feature: throw expressions. The constructor will throw an exception for negative health values. Previously this was only possible from a statement, now expressions will support it as well.

The only feature left to mention are improvements to numeric literals:

const int MAX_PLAYER_LEVEL = 0b0010_1010;

Support for binary literals has been added to the language. To make them more readable _ (underscore character) can be used as a digit separator. The latter is not limited to binary literals. It is also supported in decimal and hexadecimal literals.

Trying Out the Experimental Features

Although the above features are not yet complete, a lot of the work has already been done. You can monitor the progress on GitHub if you are interested.

You can also try out the features if you install Visual Studio “15” Preview 4, which is available for download since the end of August. Most of the code in this article will work in it. The only exceptions to this are:

  • wildcard support in type deconstruction,
  • extended support for expression bodied members, and
  • throw expressions.

To use tuples, an additional dependency is required from NuGet: System.ValueTuple. To find the package, make sure you include prerelease packages in the search:

csharp7-nuget-package

Figure 1: System.ValueTuple package on NuGet

Conclusion:

All the new C# 7.0 language features described in this article are still work-in-progress. In the final version of C# 7.0, they might be significantly different or not available at all. This article serves only as an overview of the current state of the C# Language to give you a glimpse into the future, and maybe spark enough interest to make you follow the development more closely, or even try some of the features out before they are done. By taking a more active part in the language development, you can influence it, and at the same time learn something new; potentially improving your existing coding practices even before the next version of the language is finally available.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Damir Arh has many years of experience with Microsoft development tools; both in complex enterprise software projects and modern cross-platform mobile applications. 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 answering questions on Stack Overflow. He is an awarded Microsoft MVP for .NET since 2012.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!