DotNetCurry Logo

C# 7 - What's New

Posted by: Damir Arh , on 6/7/2017, in Category C#
Views: 90262
Abstract: C# 7 is the next version of C#. This article provides an overview of the new features in C# 7.0.

This article was updated on June 06th, 2017.

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.

While there’s already ongoing development towards C# 7.1 and C# 8.0, this article provides an overview of new features that were introduced in C# 7.0.

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. Subscribe to this magazine for FREE and download all previous, current and upcoming editions.

C# 7.0 – Main theme

In all previous versions of C# (with the exception of C# 6.0 maybe) new features 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.

C# 7.0 is no exception to this rule. The language designers were 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. C# 7.0 introduces features that 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 the features belonging to each of these themes.

C# 7 – 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 was already possible in C# 6, it was 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 now also allows 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< IWeapon > 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 discards (notice the use of _ instead of variable declarations in the code below):

(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 focus on reducing the copying of data between memory locations.

Local functions allow declaration of helper functions nested inside other functions. This does 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 performance improvement in C# 7.0 targets asynchronous methods.

Currently all asynchronous methods must return Task<T>, which is a reference type. Now, a value type is 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 now allows 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 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 support it as well.

To pass a variable to an output argument of a function before C# 7.0, you needed to declare it beforehand:

ISpell spell;
if (learnedSpells.TryGetValue(spellName, out spell))
{
    spell.Cast(enemy);
}

Now you can declare the out variable directly in the function argument list:

if (learnedSpells.TryGetValue(spellName, out var spell))
{
    spell.Cast(enemy);
}

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.

Using the new C# 7 features in Visual Studio 2017

C# 7.0 is not supported in Visual Studio 2015.

To start using the new features, you need to download and install Visual Studio 2017, which was released in March of 2017. Most of the code works with new projects out-of-the-box, however some require that you install additional NuGet packages:

- To use tuples, you need to install System.ValueTuple NuGet package, which adds the required System.ValueType<> generic type:

csharp7-nuget-package

Figure 1: System.ValueTuple package on NuGet

- To use ValueTask<T>, you need to install System.Threading.Tasks.Extensions NuGet package.

Conclusion:

C# 7.0 is the first version of the language that was developed in an open-source model.

The development is continuing, though, and work on versions 7.1 and 8.0 is already underway. You can follow the development on GitHub, or even try some of the features out before they are finalized.

By taking a more active part in the C# language development, you can influence it, and at the same time learn something new; potentially improving your existing coding practices even before the new features are available in one of the future versions of the language.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
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!