C# 8.0 – New Planned Features

C# 8.0 – New Planned Features

WebDEV

I am going to introduce a selection of currently planned features which will most probably end up in the final release. All of the C# 8 features are still early in development and are likely to change.

Nullable Reference Types

This feature was already considered in the early stages of C# 7.0 development but was postponed until the next major version. Its goal is to help developers avoid unhandled NullReferenceException exceptions.

The core idea is to allow variable type definitions to specify whether they can have null value assigned to them or not:

IWeapon? canBeNull;
IWeapon cantBeNull;


Assigning a null value or a potential null value to a non-nullable variable would result in a compiler warning (the developer could configure the build to fail in case of such warnings, to be extra safe):

canBeNull = null;       // no warning
cantBeNull = null;      // warning
cantBeNull = canBeNull; // warning


Similarly, warnings would be generated when dereferencing a nullable variable without checking it for null value first.

canBeNull.Repair();       // warning
cantBeNull.Repair();      // no warning
if (canBeNull != null) {
    cantBeNull.Repair();  // no warning
}


The problem with such a change is that it breaks existing code: it is assumed that all variables from before the change, are non-nullable. To cope with this, static analysis for null safety could be enabled selectively with a compiler switch at the project level.

The developer could opt-in to the nullability checking when he/she is ready to deal with the resulting warnings. This would be in best interest of the developer, as the warnings might reveal potential bugs in his code.

There’s an early preview of the feature available to try out as a download for Visual Studio 2017 15.6 update.


Default Interface Methods

Interfaces in C# are currently not allowed to contain method implementations. They are restricted to method declarations:

interface ISample
{
    void M1();                                    // allowed
    void M2() => Console.WriteLine("ISample.M2"); // not allowed
}


To achieve similar functionality, abstract classes can be used instead:

abstract class SampleBase
{
    public abstract void M1();
    public void M2() => Console.WriteLine("SampleBase.M2");
}


In spite of this, there are plans to add support for default interface methods to C# 8.0, i.e. method implementations using the syntax suggested in the first example above. This would allow scenarios not supported by abstract classes.

A library author could extend an existing interface with a default interface method implementation, instead of a method declaration.

This would have the benefit of not breaking existing classes, which implemented an older version of the interface. If they didn’t implement the new method, they could still use the default interface method implementation. When they wanted to change that behavior, they could override it, but with no code change just because the interface was extended.


Ranges

There are plans to add new syntax for expressing a range of values:

var range = 1..5;

This would result in a struct representing the declared range:

struct Range : IEnumerable<int>
{
    public Range(int start, int end);
    public int Start { get; }
    public int End { get; }
    public StructRangeEnumerator GetEnumerator();
    // overloads for Equals, GetHashCode...
}


The new type could be effectively used in several different contexts:

- It could appear as an argument in indexers, e.g. to allow a more concise syntax for slicing arrays:

Span<T> this[Range range]
{
    get
    {
        return ((Span<T>)this).Slice(start: range.Start, length: range.End - range.Start);
    }
}


- Since the struct implements the IEnumerable interface, it could be used as an alternative syntax for iterating over a range of values:

foreach (var index in min..max)
{
    // process values
}


- Pattern matching could take advantage of the syntax for matching a value to a specified range:

switch (value)
{
    case 1..5:
        // value in range
        break;
}


It’s still undecided whether the range operator would be inclusive or exclusive, i.e. whether the resulting range would include the End value or not. It’s even possible that both an inclusive and an exclusive syntax will be available.


Default Literal in Deconstruction

To assign default values to all members of a tuple in C# 7, the tuple assignment syntax must be used:

(int x, int y) = (default, default);

With support for default literal in deconstruction syntax, the same could be achieved with the following syntax:

(int x, int y) = default;


Records

Currently, a significant amount of boilerplate C# code has to be written while creating a simple class which acts as a value container only and does not include any methods or business logic.

The records syntax should allow standardized implementation of such classes with absolute minimum code:

public class Sword(int Damage, int Durability);

This single line would result in a fully functional class:

public class Sword : IEquatable<Sword>
{
    public int Damage { get; }
    public int Durability { get; }
 
    public Sword(int Damage, int Durability)
    {
        this.Damage = Damage;
        this.Durability = Durability;
    }
 
    public bool Equals(Sword other)
    {
        return Equals(Damage, other.Damage) && Equals(Durability,  other.Durability);
    }
 
    public override bool Equals(object other)
    {
        return (other as Sword)?.Equals(this) == true;
    }
 
    public override int GetHashCode()
    {
        return (Damage.GetHashCode() * 17 + Durability.GetHashCode());
    }
 
    public void Deconstruct(out int Damage, out int Durability)
    {
        Damage = this.Damage;
        Durability = this.Durability;
    }
 
    public Sword With(int Damage = this.Damage, int Durability = this.Durability) => 
        new Sword(Damage, Durability);
}


As you can see, the class features read-only properties, and a constructor for initializing them. It implements value equality and overrides GetHashCode correctly for use in hash-based collections, such as Dictionary and Hashtable. There’s even a Deconstruct method for deconstructing the class into individual values with the tuple syntax:

var (damage, durability) = sword;

You probably don’t recognize the syntax used in the last method of the class.

Method parameter’s default argument will additionally allow referencing a class field or property using such syntax. This is particularly useful for implementing the With helper method dedicated to creating modified copies of existing immutable objects, e.g.:

var strongerSword = sword.With(Damage: 8);

Additionally the with expression syntax is considered as well, as syntactic sugar, for calling this method:

var strongerSword = sword with { Damage = 8 };


Recursive Patterns

There are plans to further extend the support in C# 8.0.

Recursive patterns will allow deconstruction of matched types in a single expression. It should work well with the compiler generated Deconstruct() method for records:

if (sword is Sword(10, var durability)) {
    // code executes if Damage = 10
    // durability has value of sword.Durability
}


Tuple patterns will allow matching of more than one value in a single pattern matching expression:

switch (state, transition)
{
    case (State.Running, Transition.Suspend):
        state = State.Suspended;
        break;
}


An expression version of the switch statement will allow terser syntax when the only result of pattern matching is assigning a value to a single variable. The syntax is not yet finalized but the current suggestion is as follows:

state = (state, transition) switch {
    (State.Running, Transition.Suspend) => State.Suspended,
    (State.Suspended, Transition.Resume) => State.Running,
    (State.Suspended, Transition.Terminate) => State.NotRunning,
    (State.NotRunning, Transition.Activate) => State.Running,
    _ => throw new InvalidOperationException()
};


Target-typed new Expression

When declaring local variables, the var keyword can already be used to avoid repeating (potentially long) type names in code:

Dictionary<string, string> dictionary = new Dictionary<string, string>(); // without var keyword

var dictionary = new Dictionary<string, string>(); // with var keyword

The same approach cannot be used for class members (e.g. fields) because they require the type to be explicitly stated:

class DictionaryWrapper
{
    private Dictionary<string, string> dictionary = new Dictionary<string, string>();
    // ...
}


The target-typed new expression, as planned for C# 8, would allow an alternative shorter syntax in such cases:

class DictionaryWrapper
{
    private Dictionary<string, string> dictionary = new();
    // ...
}


The syntax would of course not be limited to this context. It would be allowed wherever the target type could be implied by the compiler.



However, we’re probably still quite far away from the final release of C# 8.0, as the release date has not been announced yet. Until then, we can expect the plans to change: not all features might make it into the release. And even those that will make it to the final release, might still change syntactically or even semantically.

Report Page