C# 7.1, 7.2 and 7.3 - New Features (Updated)

Posted by: Damir Arh , on 5/14/2018, in Category C#
Views: 49620
Abstract: C# 7 has added a number of new features to the language. This tutorial looks at the new features added to the minor versions of C# i.e. C# 7.1, 7.2 and and 7.3.


Between the major versions, which were historically aligned with new Visual Studio versions, they started to release minor versions as part of selected Visual Studio 2017 updates. Minor versions include smaller new features, which don’t require changes to the Common Language Runtime (CLR).

Larger features will still be released with major versions (v7, v8 and so on) only.

If you are interested in what's new and upcoming in C# 8.0, read C# 8 - New Planned Features.

Using Minor Versions of C# (7.1, 7.2 and 7.3) in Visual Studio

C# 7.1 was released in August 2017 as part of the 15.3 update for Visual Studio 2017, and C# 7.2 was released in December 2017 with Visual Studio 2017 15.5. Unlike new language releases in the past, this time their new features are not automatically enabled after updating Visual Studio; neither in existing projects, nor when creating a new project.



Figure 1: Build error for new language features

The language version can be changed in the project properties.


Editorial Note: If you are new to C# 7, read our tutorial at www.dotnetcurry.com/csharp/1286/csharp-7-new-expected-features

We can select a specific version instead (7.1 to get the missing feature above) or the latest minor version, which will always automatically use the latest version currently available.


Figure 2: Changing the language version

The latter option is not selected by default. This is so that development teams can control how they will adopt new minor language versions.

If new language features were automatically available, this would force everyone in the team to update Visual Studio as soon as a single new feature was used for the first time, or the code for the project would not compile.


Pro TIP: When changing the language version in the project properties, make sure you do it for each configuration, or even better: set the Configuration on the Build tab to All Configurations before applying the change. Otherwise you might end up changing the language version for the Debug configuration only, causing the build to fail for the Release configuration.


Figure 3: Configuration selection on Build tab



Figure 4: Code fix for changing the language version

C# 7.1 – What’s New

Async Main

Support for an asynchronous Main() function was already considered for C# 7.0, but was postponed until C# 7.1. The feature simplifies using asynchronous methods with async and await syntax in console applications. Before C# 7.1, the Main method as the program entry point supported the following signatures:

public static void Main();
public static int Main();
public static void Main(string[] args);
public static int Main(string[] args);


static void Main(string[] args)

static async Task MainAsync(string[] args)
    // asynchronous code

With C# 7.1, the Main method supports additional signatures for asynchronous code:

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);


Default Literal Expressions


int numeric = default(int);         // = 0
Object reference = default(Object); // = null


bool IsDefault<T>(T value)
    T defaultValue = default(T);
    if (defaultValue != null)
        return defaultValue.Equals(value);
        return value == null;

C# 7.1 adds support for a default literal expression, which can be used instead of the default value expression whenever the type can be inferred from the context:

int numeric = default;
Object reference = default;
DateTime value = default;
T defaultValue = default;


  • in a return statement,
  • as the default value for optional parameters,
  • as the argument value when calling a method.


Inferred Tuple Element Names


When creating a tuple in C#, element names had to be explicitly given or the elements could only be accessed via the default names, i.e. Item1, Item2 etc.:

var x1 = coords1.x;

var coords2 = (x, y);


var coords2 = (x, y);
var x2 = coords2.x;

Generic Pattern Matching


void Attack(IWeapon weapon, IEnemy enemy)
    switch (weapon)
        case Sword sword:
            // process sword attack
        case Bow bow:
            // process bow attack


    switch (weapon)
        case Sword sword:
            // process sword attack
        case Bow bow:
            // process bow attack


C# 7.2 – What’s New

Digital Separator after Base Specifier


var dec = 1_000_000;
var hex = 0xff_ff_ff;
var bin = 0b0000_1111;

Additionally, C# 7.2 allows separators after the base specifier:

var hex = 0x_ff_ff_ff;
var bin = 0b_0000_1111;

Non-trailing Named Arguments

Named arguments were added to C# in version 4. They were primarily the tool to allow optional arguments: some parameters could be skipped when calling a method, but for all the parameters following the skipped ones, the arguments had to be named so that the compiler could match them:

    // method implementation

// method call
WriteText("Hello world", centered: true);



However, until C# 7.2, positional arguments weren’t allowed to follow named arguments in the same method call:

WriteText("Hello world", bold: true, true); // not allowed before C# 7.2

In C# 7.2, this is now a valid method call.

Positional arguments are allowed even if they follow a named argument, as long as all the named arguments are still in their correct position. The names are only used for code clarification purposes.

Private Protected

The Common Language Runtime (CLR) supports a class member accessibility level that had no equivalent in the C# language before version 7.2 and thus couldn’t be used: a protectedAndInternal member can be accessed from a subclass, but only if the subclass is within the same assembly as the base class declaring the member.

In C# 7.1 and earlier, the base class developer had to choose between two access modifiers that don’t match this behavior exactly:


One could use both access modifiers, i.e. protected internal, but that would relax the restrictions even more: the member would be visible to subclasses in any assembly, and additionally also to all classes in the same assembly.

C# 7.2 introduces a new access modifier: private protected matches the protectedAndInternal accessibility level – members will only be visible to subclasses in the same assembly. This is particularly useful to library developers who don’t need to choose between exposing protected members outside the library and making internal members available to all classes inside their library.

Ref Conditional Expression


However, there was no way to conditionally bind a variable by reference to a different expression, similar to what the ternary operator (also known as the conditional operator) does when binding by value:

var max = a > b ? a : b;


ref var max = ref b; // requires initialization
if (a > b)
    max = ref a;       // not allowed in C# 7.2

For some cases the following method could work as a replacement:

    if (condition)
        return ref trueExpression;
        return ref falseExpression;
// method call




With the ref conditional expression that was introduced in C# 7.2, the described behavior can now be achieved. Just like with the existing conditional operator, only the selected alternative will be evaluated:


Reference Semantics for Value Types


There was no way to express that in C# before version 7.2, therefore the intention could only be explained in documentation or code comments, which was purely informal and without assurance.

To address this issue, C# 7.2 includes support for read-only parameters passed by reference:

static Vector3 Normalize(in Vector3 value)
    // returns a new unit vector from the specified vector
    // signature ensures that the input vector cannot be modified

The compiler will prevent any changes to the input parameter if it is a struct. Assignments to its fields and properties won’t compile. For method invocations, a defensive copy will be used because the compiler can’t determine whether they will modify the parameter.

Unlike the other two types of parameters passed by reference (ref and out), the use of the in keyword when invoking such a method is optional. However, without it, the compiler will prefer using the overload with parameters passed by value if it exists because it is considered a better match.

This feature also allows passing literals as read-only parameters by reference:

var result = Normalize(new Vector3(1, 1, 1));

The feature is not restricted to structs. It will also work with reference types, but is most beneficial when used with structs as it can avoid unnecessary copying of values.

To further help the compiler with code optimization, another new feature can be used: read-only structs. They will only compile if they are immutable.

readonly struct ReadonlyStruct
    public int ImmutableProperty { get; }
    // public int MutableProperty { get; set; } // will not compile

When used as read-only parameters by reference, the compiler doesn’t need to create defensive copies for invoking the methods of these structs, as it knows that they cannot modify the struct.

Read-only return values by reference are somewhat similar to read-only parameters by reference.

struct Vector3
    // other struct members omitted for brevity

    private static readonly Vector3 zero = new Vector3(0, 0, 0);
    public static ref readonly Vector3 Zero => ref zero;

They return a reference to the caller instead of a copy and the compiler doesn’t allow it to be modified if it is assigned to a variable which is also declared as ref readonly. When the value is assigned to a variable that’s not ref readonly, it will be copied and this restriction will be removed:

ref readonly var zero = ref Vector3.Zero;
// zero.X = 1; // will not compile
var copy = zero;
copy.X = 1;

The final new feature is support for structs which must be allocated on the stack, i.e. ref struct types. This imposes several restrictions on how they can be used:

  • They can’t be boxed, e.g. by casting to object type or assigning to a dynamic-typed variable.
  • They can’t be members of classes or regular structs.
  • They can’t be used in lambda expressions or local functions.
  • They can’t be used in iterators or asynchronous methods.

All of this makes them safe for interop use with non-managed APIs. The main motivation for them was the implementation of the Span<T> type, which provides an array-like abstraction over a part of contiguous memory, such as a larger array, some memory on stack or even memory originating from native code. This allows processing of sub-arrays in-place, without any copying of data, making it much more efficient.

var array = new int[] { 1, 2, 3, 4, 5 }; // initialize array
Span<int> span = array; // implicit cast
var subSpan = span.Slice(start: 1, length: 3); // reference part of the array
(subSpan[0], subSpan[2]) = (subSpan[2], subSpan[0]); // swap items
// array = { 1, 4, 3, 2, 5 }

Span is available in the System.Memory standalone NuGet package which is currently still in preview. It will be included in .NET Core 2.1. To use it in the .NET framework, the NuGet package will need to be installed.

C# 7.3 – Features in Development

The language development didn’t stop with the release of C# 7.2. The team is already finalizing the next minor version – 7.3. The updated compiler supporting the new features is already included in the latest Visual Studio 2017 version 15.7 Preview 4, which can safely be installed alongside the current Visual Studio 2017 release.

Several new language features are currently planned for C# 7.3 and although they are already available to try out, some last minute changes are still possible.

Ref Local Reassignment

Local variables and parameters bound by reference are planned to be extended with another feature in C# 7.3 – the ability to rebind them to a different expression. With this change, we can write the following code to implement the functionality equivalent to the ref conditional expression introduced in C# 7.2:

ref var max = ref b; 
if (a > b)
    max = ref a; 

Additional Generic Constraints

There is a concept of unmanaged or blittable types in the Common Language Runtime, which have the same representation in managed and unmanaged memory. These are value types, which don’t contain a reference type directly or indirectly. Such structs may only contain basic types (numerical types and pointers) and other unmanaged structs.

In C# 7.3, there is a plan to introduce unmanaged as a new generic constraint which can only be matched by structs satisfying the above criteria. This would allow the implementation of generic helper functions, which could work with any unmanaged struct:

Span<byte> Serialize<T>(in T value) where T : unmanaged
    // common serialization code

Developers would not need to write the same plumbing code for each type. The same method could be called for all unmanaged types, but would not compile when used on any type not satisfying the constraint.

There’s currently also no way to write generic helper methods for handling delegates because there’s no matching generic constraint. In C# 7.3, System.Delegate is planned to be added as a constraint for delegate types. This would for example make it possible to write an extension method for type-safe combining of delegates:

public static TDelegate Combine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : Delegate
    return (TDelegate) Delegate.Combine(source, target);

Similarly, a type constraint for enum types would enable generic helper methods for enum types in C# 7.3. One of the use cases could be a common type-safe extension method for extracting a value from an attribute applied to an enum value:

public static string GetDescription<TEnum>(this TEnum value) where TEnum : Enum
    var type = typeof(TEnum);
    var member = type.GetMember(value.ToString());
    var attributes = member[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
    return ((DescriptionAttribute)attributes[0]).Description;

Attributes Targeting Fields of Auto-Implemented Properties

There’s currently no way to apply an attribute to a backing field of an auto-implemented property. The only way to achieve that is by implementing the property manually:

private double x;
public double X
    get => x;
    set => x = value;

In C# 7.3, this would not be necessary any more. Instead, a special syntax would be used to indicate that the attribute is targeting the underlying field.

[field: NonSerialized]
public double X { get; set; }

It’s a less known fact that this would not be a new syntax. The above code only triggers a warning since the very first version of the C# compiler because the same syntax is already allowed for targeting the backing field for events:

[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;

Expression Variables in Initializers

C# 7 introduced expression variables, i.e. the possibility to declare a variable inside an expression when using pattern matching or invoking methods with parameters by reference using the out keyword. They could be used almost anywhere, e.g. even in expression-bodied properties:

public int Arrows => weapon is Bow bow ? bow.Arrows : 0;
public int Number => Int32.TryParse(input, out int value) ? value : 0;

However, expression variables can’t be used in field initializers, therefore the following code does not compile, although it looks very similar:

private int arrows = weapon is Bow bow ? bow.Arrows : 0;
private int number = Int32.TryParse(input, out int value) ? value : 0;

In C# 7.3, the support for expression variables is planned to be expanded to all remaining contexts where they are not allowed yet: expression-bodied constructors, field initializers, and LINQ query clauses. This would make the code above valid C#.

Equality Operators for Value Tuples

When value tuples were introduced in C# 7, their Equals method was implemented in a typical manner for value types: two value tuples are equal if they have the same number of members and each two corresponding members are of the same type and equal according to their default equality comparer. The built-in code for testing a single item for equality between two tuples behaves equivalently to the following implementation:

var item1Equal = tuple1.Item1?.GetType() == tuple2.Item1?.GetType() && 
    ((tuple1.Item1 == null && tuple2.Item1 == null) || tuple1.Item1.Equals(tuple2.Item1));

With C# 7.3, there are plans to also implement equality and inequality operators (i.e. == and !=) for value tuples in a similar manner. Of course, the built-in code for comparing the items will use the equality operator instead of the equality comparer and the type comparison. It will behave the same as the following implementation for comparing a single item:

var item1Equal = tuple1.Item1 == tuple2.Item1;




In accordance with the open source model, even the state of upcoming features in future versions of the language is public and available for all to explore or even contribute their opinion to. It’s important to keep in mind though, that these features are still work in progress and as such they could change without warning or even be postponed to a later version.

This article was technically reviewed by Yacoub Massad.


This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET





What Others Are Reading!
Share on LinkedIn
Share on Google+


Page copy protected against web site content infringement 	by Copyscape






C# Book for Building Concepts and Interviews



jQuery CookBook