Der nächste Halbton: C# 7.3

Mit der dritten und vermutlich letzten Unterversion der Programmiersprache C# 7 hat Microsoft keine großen Neuerungen eingeführt, aber einige Sprachfeatures erweitert.

Sprachen  –  3 Kommentare
Der nächste Halbton: C# 7.3

Bereits im Dezember 2017 berichtete ein Artikel auf heise Developer, dass Microsoft nun die Programmiersprache C# ebenso wie viele andere Produkte in kleinen Schritten entwickelt. Die Version 7.3 ist solch ein Schritt und offenbar der letzte vor der nächsten Hauptversion 8.0.

Vergleich von Tupeln

In C# 7.0 hatte Microsoft Value Tupel als leichtgewichtige, unbenannte Datenstruktur eingeführt, die die Runtime auf dem Stack speichert – im Gegensatz zu dem in .NET Framework 4.0 eingeführten Referenztyp System.Collections.Tupel. Erst mit Version 7.3 erlaubt die Programmiersprache den direkten Vergleich zweier Tupel mit den Vergleichsoperatoren == und !=:

var p = (ID: 1, Name: "Holger Schwichtenberg", 
DOTNETExperte: true);
// ...

if (p == (1, "Holger Schwichtenberg", true))
{
Console.WriteLine("Er ist es :-)");
}

if (p != (1, "Holger Schwichtenberg", true))
{
Console.WriteLine("Er ist nicht :-(");
}

Dabei sind nicht nur eine Variable und ein Tupel-Ausdruck, sondern auch zwei Tupel-Ausdrücke vergleichbar. So lässt sich anstelle von

if (x == 1 && y == 2) 
{
Console.WriteLine("x ist 1 und y ist 2!");
}

auch Folgendes formulieren:

if  ((x, y)  ==  (1, 2)) 
{
Console.WriteLine("x ist 1 und y ist 2!");
}

Lokale Zuweisung

Microsoft hatte in C# 7.0 ebenfalls Managed Pointer mit dem Schlüsselwort ref als Alternative zur Zeigerprogrammierung in unsafe-Blöcken eingeführt:

int i = 32;
int k = 42;
ref int z = ref i;

Console.WriteLine("z=" + z);

z += 5;

Console.WriteLine("z=" + z);

Die Ausgabe ist erst z=32 und anschließend z=37, da z+=5 nicht den Zeiger verschiebt, sondern den Wert ändert. Bisher nicht erlaubt war es, einen bestehenden Managed Pointer an eine andere Speicherstelle neu zuzuweisen. Folglich bemängelte der Compiler nachstehende Ergänzung in C# 7.0 bis 7.2:

z = ref k; 
Console.WriteLine("z=" + z);

Das ist aber in Version 7.3 nun möglich und nennt sich "Ref Local Reassignment", sodass die dritte Ausgabe z=42 lautet.

Ergänzungen für unsafe-Blöcke

Für unsafe-Blöcke hat Microsoft Verbesserungen eingebaut. Das Allokieren von Speicher auf dem Stack mit stackalloc war bisher nicht in Verbindung mit einer prägnanten Array-Initialisierung erlaubt. Erst ab C# 7.3 funktioniert folgender Code:

unsafe
{
var a2 = stackalloc int[3] { 45, 2, 57 }; // ab C# 7.3
var a3 = stackalloc int[] { 45, 2, 57 }; // ab C# 7.3
var a4 = stackalloc[] { 45, 2, 57 }; // ab C# 7.3
}

Zuvor mussten Entwickler die Array-Elemente mühsam einzeln initialisieren:

unsafe
{
var a1 = stackalloc int[3]; // bisher schon erlaubt
a1[0] = 45;
a1[1] = 2;
a1[2] = 57;
}

Die verkürzte Array-Initialisierung kann nun auch außerhalb von unsafe-Blöcken in Verbindung mit den in C# 7.2 eingeführten Typ Span<T> zum Einsatz kommen:

Span<int> a5 = stackalloc[] { 1, 2, 3 };// ab C# 7.3

Seit der ersten Version von C# gibt es mit fixed deklarierte Variablen, die der Garbage Collector nicht verschiebt. Sie dürfen nur in Strukturen (struct), nicht jedoch in Klassen (class) vorkommen. Als "Indexing movable fixed Buffers" bezeichnet Microsoft die Option, dass mit fixed deklarierte Variablen einfacher zu handhaben sind.

Das Befüllen und Auslesen eines fixed Array erforderte in C# bis einschließlich Version 7.2 immer einen zusätzlichen fixierten Zeiger, wie folgender Code zeigt:

unsafe struct Daten
{
public fixed int Zahlen[7];
}

/// <summary>
/// vor C# 7.2
/// </summary>

class BerechnungAlt
{
static Daten s = new Daten();
unsafe public void Berechnen()
{
fixed (int* ptr = s.Zahlen)
{
for (int i = 0; i < 7; i++)
{
ptr = new System.Random().Next(1, 49);
}
int p1 = ptr[5];
Console.WriteLine(p1);
}
}
}

Ab C# 7.3 kann man darauf verzichten:

/// <summary>
/// Ab C#7.3
/// </summary>
class BerechnungNeu
{
static Daten s = new Daten();
unsafe public void Berechnen()
{
for (int i = 0; i < 7; i++)
{
// geht nicht vor C# 7.3:
s.Zahlen[i] = new System.Random().Next(1, 49);
}
int p2 = s.Zahlen[5]; // geht nicht vor C# 7.3
Console.WriteLine(p2);
}
}