C# 7.0 What’s new. Part 1

In March 2017 Microsoft introduced the new generation of C# – version 7. The new version not only provided additional features, but also facilitated further code simplification. Below is a short review of the most significant novelties being introduced in C# version 7:

1. Tuple

Tuples are commonly used in the following ways:

  • To provide easy access to multiple values associated with one key. Of course, it is possible to use the generic Dictionary Class and retrieve the values as objects of another custom, but such approach does not provide an acceptable solution for OLAP/BI applications, in which tuples typically represent a multidimensional data subset.
  • To return multiple values from a method without out parameters
  • To pass multiple values to a method through a single parameter. A typical example might be the call of Thread.Start(object parameter) that expects only a single parameter. By means of tuples we may pass many values under the same parameter without excessive class creation.

Prior to that .NET CTS provided similar feature by System.Tuple<> reference type. This approach imposed the following limitations:

  • Access to the associated values in tuples was done via fixed properties names Item1…Item8 w/o any option to rename them
  • The maximum number of associated values in tuple that CTS provides was 8
  • System.Tuple<> is a reference type , meaning it makes an additional pressure on Garbage Collection process that may lead to performance hit

Now they are supported at the language level. For example, we may write this literal:

var person = (Name: “Terry McGraw”, Age: 73);
Console.WriteLine(person.Name); // Terry McGraw

Or implement an anonymous returning type:

(string Name, int AgeYears) GetPersonRecord()
{

var name = “Patrice Talbot”;
var age = 32;
return (name, age);

}

var person = GetPersonRecord();
Console.WriteLine(person.Name); // Patrice Talbot

In addition to that, it’s possible to specify properties’ names in the returned tuple explicitly:

return (firstName: first, lastName: last, SSN: personSSN);

The last change in tuple support it CTS: Tuple becomes a value type, meaning there’s no time spent on GC. This means the ValueTuple can be used in the more performance critical code.

2. Discards

Often when taking a tuple value or calling a method with out parameters, you have to define a dedicated variable per each property in the tuple event if there’s no need in its value later. From now on C# provides support for this case as discard. A discard is a write-only fictive variable with name _ (underscore); below is a sample of possible usage:

(string, double, int, int, int, int) GetCityDataForYears(string cityName, int yearFrom, int yearTo)
{
}
var (_, _, _, pop1, _, pop2) = GetCityDataForYears(“Santa Barbara”, 1960, 2010);

3. ValueTask

Before release of C# 7 every async method should either return void, Task or Task. Being a reference type, Task may lead to performance hit in case of intensive GC calls. In C# 7.0 type Task is already declared as a value type and thus, CLR won’t spent time for deallocation of the corresponding objects.

4. Local functions

In Delphi/Object Pascal it’s common to have function defined in scope of other function and unavailable outside of the surrounding parent function. Now this feature becomes available in C#:

IEnumerable FromA2Z(char start, char end)
{

if (start < ‘A’ || start > ‘Z’)

throw new ArgumentOutOfRangeException(“start must be a A-Z”);

if (end < ‘A’ || end > ‘Z’)

throw new ArgumentOutOfRangeException(“end must be a A-Z”);

if (end <= start)

throw new ArgumentException($”{nameof(end)} must be greater than {nameof(start)});

return getCharSeq();

IEnumerable<char> getCharSeq()
{

for (var c = start; c < end; c++)
yield return c;

}

}

All parameters and internal variables used in enclosing function are still available in the internal one.

Note: local functions have additional advantage over closures – the last ones require allocation in managed heap resulting in performance degradation risk under high load. Local functions, on the contrary, don’t affect managed heap and thus, may be a good fit in applications where low latency is crucial.

5. Switch with patterns

The switch keyword got new extended syntax to support execution branching depending on variable pattern matching. Now it may have the following look:

public class Locomotive
{

public int OwnWeight;
public int Power;

}

public class DieselLocomotive : Locomotive
{

public int FuelAmt;
public int TransmissionPower;

}

public class ElectricLocomotive : Locomotive
{

public int ElectricPower;
public int Voltage;
public bool IsAC;

}

public class PassangerDieselLocomotive: DieselLocomotive
{

public int VagonVoltage;

}

void DumpInfo(Locomotive shape)
{

var sForDump = string.Empty;
switch (shape)
{

case DieselLocomotive rsq when (rsq.TransmissionPower >= 0.5 *rsq.Power):

sForDump = $”High Efficient Diesel Locomotive, Power: {rsq.Power} , PTR: {rsq.TransmissionPower / rsq.Power};
break;

case DieselLocomotive c:

sForDump = $”Diesel Locomotive, Power: {c.Power};
break;

case PassangerDieselLocomotive r:

sForDump = $”Passanger Diesel Locomotive, Power: {r.Power}, Voltage for passanger vagons: {r.VagonVoltage};
break;

case ElectricLocomotive rsq when (rsq.IsAC && rsq.Voltage == 3):

sForDump = $”Short Distance Electric Locomotive AC, Power(electric): {rsq.ElectricPower};
break;

case ElectricLocomotive rsq when (!rsq.IsAC):

sForDump = $”Electric Locomotive DC, Power(electric): {rsq.ElectricPower};
break;

case ElectricLocomotive rsq when (rsq.IsAC && rsq.Voltage == 25):

sForDump = $”Long Distance Electric Locomotive AC, Power(electric): {rsq.ElectricPower};
break;

default:

sForDump = “Locomotive type is not classified yet”;
break;

case null:

sForDump = “Unknown parameter type”;
break;

}
Debug.WriteLine(sForDump);

}

Aside from switch syntax’s enriching, the following notes may be given:

  • The order of case clauses becomes significant now. It resembles the catch clause, i.e. the least specific clause is evaluated before the more specific. Moreover, if to leave the switch shown above in unchanged order, the compile-time error will take place:error CS8120: The switch case has already been handled by a previous case.This error may be fixed if to change the corresponding lines order:

void DumpInfo(Locomotive shape)
{

var sForDump = string.Empty;
switch (shape)
{

case DieselLocomotive rsq when (rsq.TransmissionPower >= 0.5 *rsq.Power):

sForDump = $”High Efficient Diesel Locomotive, Power: {rsq.Power} , PTR: {rsq.TransmissionPower / rsq.Power};
break;

case PassangerDieselLocomotive r:

sForDump = $”Passanger Diesel Locomotive, Power: {r.Power}, Voltage for passanger vagons: {r.VagonVoltage};
break;

case DieselLocomotive c:

sForDump = $”Diesel Locomotive, Power: {c.Power};
break;

case ElectricLocomotive rsq when (rsq.IsAC && rsq.Voltage == 3):

sForDump = $”Short Distance Electric Locomotive AC, Power(electric): {rsq.ElectricPower};
break;

case ElectricLocomotive rsq when (!rsq.IsAC):

sForDump = $”Electric Locomotive DC, Power(electric): {rsq.ElectricPower};
break;

case ElectricLocomotive rsq when (rsq.IsAC && rsq.Voltage == 25):

sForDump = $”Long Distance Electric Locomotive AC, Power(electric): {rsq.ElectricPower};
break;

default:

sForDump = “Locomotive type is not classified yet”;
break;

}

Debug.WriteLine(sForDump);

}

  • There’s one exception from this rule: the default clause is always evaluated last, even if was put in the middle of switch clause, which is allowed by syntax, but may not be a good idea semantic-wise.
  • Pattern variables such as rsq, с and r in the sample above are visible only in the corresponding case section until the nearest break.

6. Patter-based “Is”

The keyword “is” was enhanced in the new version as well. Now it’s possible to avoid declaration of one-time variable in those statements:

if (o is Rect pt)
{

var c = new ImageDrawing();
c.Rect = pt;

}

In this clause the variable pt is available only inside of the enclosing {}, and we don’t need to declare it before the “if”.

7. Literal improvements

C# 7 introduces new way of enhancing readability of long numbers via separating its parts with symbol “_”.

var d1 = 7_499_61540_17;
var d2 = 7495_486_4017;
var x = 0xBE_EF_7213;

The “_” symbol may be written at any location; it doesn’t affect the value interpretation, but only its readability.

Also in C# 7 it becomes easier to declare binary values that might be used as a mask for Boolean operations:

var b1 = 0b1110_1001_1100_1101_0110_1101_0001_0101_1101_0111;

8. Expression-bodied members

C# 6 introduced a short way of writing bodies (named in MS docs as expression-bodied) for read-only properties and member functions. In C# 7.0 this idea was extended to constructors, destructors, all properties and indexers:

private string label;
public string TicketLabel
{

get => label;
set => this.label = value ?? “Default label”;

}

public TicketWindow(string label) => this. TicketLabel = label;
~TicketWindow() => Debug.WriteLine(“Shutting down…”);

private Dictionary<string, DateTime> spikeDates;

public DateTime this[string blueChipName] => spikeDates[blueChipName];

Share this post