Sie sind hier:
Wissen
Telefon (Mo-Fr 9 bis 16 Uhr):
0201/649590-0
|
Kontaktformular
MENU
Medien
Übersicht
Lexikon/Glossar
Spickzettel
Weblog
Konferenzvorträge
Fachbücher
Fachartikel
Leserportal
Autoren gesucht!
Literaturtipps
Praxisnahe Fallbeispiele
Downloads
Newsletter
.NET
Startseite
.NET 8.0
.NET 7.0
.NET 6.0
.NET 5.0
.NET Core
.NET 4.0/4.5.x/4.6.x
.NET 3.0/3.5
.NET 2.0
.NET-Lexikon
Programmiersprachen
Entwicklerwerkzeuge
Klassenreferenz
Softwarekomponenten
Windows Runtime
World Wide Wings-Demo
Versionsgeschichte
Codebeispiele
ASP.NET
Artikel
Bücher
Schulung & Beratung
Konferenzen/Events
ASP.NET
Startseite
Lexikon
Sicherheit
Konfiguration
Global.asax
Tracing
Technische Beiträge
Klassenreferenz
Programmiersprachen
Entwicklerwerkzeuge
Softwarekomponenten
Forum
Schulung & Beratung
PowerShell
Startseite
Commandlet-Referenz
Codebeispiele
Commandlet Extensions
Versionsgeschichte
Schulungen+Beratung
Windows
Startseite
Windows Runtime (WinRT)
Windows PowerShell
Windows Scripting
Windows-Schulungen
Windows-Lexikon
Windows-Forum
Scripting
Startseite
Lexikon
FAQ
Bücher
Architektur
Skriptsprachen
Scripting-Hosts
Scripting-Komponenten
COM/DCOM/COM+
ADSI
WMI
WMI-Klassenreferenz
Scripting-Tools
WSH-Editoren
Codebeispiele
.NET-Scripting
Forum
Schulung & Beratung
Nutzer
Anmeldung/Login
Buchleser-Registrierung
Gast-Registrierung
Hilfe
Website-FAQ
Technischer Support
Site Map
Tag Cloud
Suche
Kontakt
Erklärung des Begriffs: CSharp 12.0 (C# 12.0)
Begriff
CSharp 12.0
Abkürzung
C# 12.0
Eintrag zuletzt aktualisiert am
13.11.2023
Zur Stichwortliste unseres Lexikons
Was ist
CSharp 12.0
?
Die zwölfte Sprachversion (C# 12.0) ist zusammen mit
.NET 8.0
am 14.11.2023 erschienen.
Gewertete
Liste
der neuen Sprachfeatures in C# 12.0
Die wichtigsten Neuerungen in C# 12.0 sind:
Primärkonstruktoren
Vereinfachte Initialisierung und Zuweisung für Mengen (Collection Expressions / Collection
Literal
s)
Typaliase
Optionale Lambda-Parameter
Parametermodifizierer ref readonly
Erweiterter Einsatz von nameof()
Support für C# 12.0
C# 12.0 wird offiziell von Microsoft erst ab
.NET 8.0
unterstützt ("C# 12.0 is supported only on .NET 8 and newer versions." [
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version
]. Man kann allerdings die meisten (aber nicht alle!) C# 12.0-Sprachfeatures auch in älteren .NET-Versionen einschließlich
.NET Framework
,
.NET Core
und
Xamarin
nutzen. Dazu muss man die <LangVersion> in der Projektdatei auf "12.0" erhöhen.
Notwendige Visual Studio-Version für C# 12.0 ist
Visual Studio 2022
v17.8 oder höher. Eine Verwendung von C# 12.0 ist sowohl mit
Visual Studio for Mac
2022 als auch einer aktuellen Version von
Visual Studio Code
und anderen OmniSharp-kompatiblen Editoren [
https://www.omnisharp.net
] möglich.
Primärkonstruktoren in C# 12.0
Die wesentliche, seit
.NET 8.0
Preview 2 implementierte Neuerung in C# 12.0 sind die Primärkonstruktoren für Klassen. Alte Hasen unter den C#-Entwicklern werden sich erinnern, dass dieses Sprachfeature bereits im Jahr 2014 als
Prototyp
für
C# 6.0
verfügbar war, dann aber doch gestrichen wurde.
Nun, sechs C#-Versionen weiter, kommt Microsoft in C# 12.0 darauf zurück, auch vor dem Hintergrund der Record-Typen, die es seit
C# 9.0
mit Primärkonstruktoren gibt:
public record Person(int ID, string Name, string Website = "");
Ein Primärkonstruktor ist eine Parameterliste direkt hinter dem
Typname
n. In C# 12.0 ist das auch für Klassendefinitionen möglich:
public class Person(int ID, string Name, string Website = "");
Solch eine Klasse kann ohne Inhaltsbereich (also geschweifte Klammern) existieren, ist aber wertlos. Anders als bei den in
C# 9.0
eingeführten Record-Typen erstellt der Primärkonstruktor nämlich keine öffentlichen Properties in der Klasse, sondern nur private
Field
s. Wenn man diese Klasse mit Primärkonstruktor in einem
Decompiler
betrachtet, sieht man zunächst überhaupt keine Verarbeitung der Parameter im Primärkonstruktor:
public class Person
{
public Person(int ID, string Name, string Website = "") { }
}
Das liegt daran, dass die Primärkonstruktorparameter gar nicht verwendet werden. Wir müssen die Klasse z.B. um ToString() erweitern, siehe Listing.
Listing: Klasse mit Primärkonstruktor und
Methode
ToString(), in der alle Primärkonstruktoren verwendet werden
public class Person(int ID, string Name, string Website = "")
{
public override string ToString()
{
return $"Person #{ID}: {Name} -> {Website}";
}
}
Nun sehen wir im
Decompiler
, dass für jeden Primärkonstruktorparameter ein privates
Field
angelegt wurde inklusive Zuweisung im Konstruktor.
Listing: Decompilat des vorherigen Listings mit ILSpy
public class Person
{
[CompilerGenerated]
[
Debugger
Browsable(
Debugger
BrowsableState.Never)]
private int <ID>PC
Backing
Field
;
[CompilerGenerated]
[
Debugger
Browsable(
Debugger
BrowsableState.Never)]
private string <Name>PC
Backing
Field
;
[CompilerGenerated]
[
Debugger
Browsable(
Debugger
BrowsableState.Never)]
private string <Website>PC
Backing
Field
;
public Person(int ID, string Name, string Website = "unbekannt")
{
<ID>PC
Backing
Field
= ID;
<Name>PC
Backing
Field
= Name;
<Website>PC
Backing
Field
= Website;
base.ctor();
}
public override string ToString()
{
return $"Person #{<ID>PC
_Backing
Field
}: {<Name>PC__Backing
Field
} -> {<Website>PC_
Backing
Field
}";
}
}
Um öffentlich auf die im Primärkonstruktor übergebenen Daten zugreifen zu können, muss man die Konstruktorparameter für Zuweisungen verwenden, siehe Name und Website im nächsten Listing. Im Listing gibt es neben der Klasse Person eine zweite, abgeleitete Klasse Autor mit Primärkonstruktor.
Listing: Primärkonstruktorbeispiel mit Zuweisung der Primärkonstruktorparameter an öffentliche Properties und
Vererbung
using
System.ComponentModel
.Data
Annotation
s;
using ITVisions;
namespace NET8Konsole.CS12;
/// <summary>
/// Klasse mit Primärkonstruktor
/// </summary>
public class Person(Guid personGuid, string name)
{
public Guid PersonGuid { get; set; } = personGuid;
public string Name { get; set; } = name;
public Person() : this(Guid.Empty, "")
{
// Aufruf ohne Parameter möglich, führt aber zu einem ungültigen Objekt ;-)
}
public override string ToString()
{
return $"Person {personGuid}: {Name}";
}
}
/// <summary>
/// Abgeleitete Klasse mit Primärkonstruktor
/// </summary>
public class Autor(Guid personGuid, string name, string website) : Person(personGuid, name)
{
public string Website { get; set; } = website;
public override string ToString()
{
return $"Autor {personGuid}: {Name} -> {Website}";
}
}
internal class CS12
PrimaryConstructors
Demo
{
public void Run()
{
var p = new Person();
Console.WriteLine(p.Name);
Console.WriteLine(p.ToString());
var a = new Autor(Guid.NewGuid(), "
Dr. Holger Schwichtenberg
", "www.IT-Visions.de");
Console.WriteLine(a.Name);
Console.WriteLine(a.Website);
Console.WriteLine(a.ToString());
}
}
Vereinfachte Initialisierung und Zuweisung für Mengen (Collection Expressions / Collection
Literal
s)
Eine sehr schöne Neuerung in C# 12.0 ist die vereinfachte Syntax für die Initialisierung von Mengen. Microsoft nannte dieses Sprachfeature ursprünglich Collection
Literal
s, dann Collection Expressions.
Mit dieser neuen Syntaxform kann man die bisher sehr heterogene Initialisierungsformen von
Objektmenge
n stark vereinheitlichen im Stil von
JavaScript
, also mit den Werten in eckigen Klammern, getrennt durch Kommata.
Beispiele
int[] a = [1,2,3];
Span<int> b = [1,2,3];
List<int> d = [1,2,3];
Ienumerable<int> e = [1,2,3];
Die Syntax mit den eckigen Klammern ist nicht nur bei der Erstinitialisierung, sondern auch bei späteren Zuweisungen möglich:
string[] sites1, sites2, sites3 = ["angular-schulungen.de", "dotnettraining.de"];
sites1 = ["www.dotnetframework.de", "www.dotnet8.de", "dotnet-lexikon.de"];
sites2 = ["www.dotnet-doktor.de", "www.powershell-schulungen.de"];
Mit dem Spread-Operator .. kann man im Rahmen der Initialisierung Mengen in andere Mengen integrieren. Der Spread-Operator sorgt dafür, dass keine verschachtelte, sondern eine flache
Liste
entsteht!
// Array aus den Elementen der Arrays erstellen mit Spread Operator
string[] allSitesAsArray = [.. sites1, .. sites2, "www.IT-Visions.de", .. sites3];
//
Liste
aus den Elementen der Arrays erstellen mit Spread Operator
List<string> allSitesAsList = [.. sites1, .. sites2, "www.IT-Visions.de", .. sites3];
//
Liste
noch mal erweitern
allSitesAsList = [.. allSitesAsList, "powershell-schulungen.de"];
// Auflisten: 7 Sites sind nun in der
Liste
foreach (var site in allSitesAsList)
{
Console.WriteLine(site);
}
Es entsteht eine Menge mit diesen acht Websites, denn neben den sieben in den
Variable
s sites1, sites2 und sites3 enthaltenen Websites wurde noch eine weitere Website hinzugefügt.
Typaliase in C# 12.0
Seit C# 12.0 gibt es mit Typaliasen die Möglichkeit, für einen Typen einen alternativen Namen zu definieren. Typen können dabei C#-Typen, .NET-Basisklassen/-Strukturen oder eigene Klassen/Strukturen sein.
Einmal mehr kommt dabei das Schlüsselwort using zum Einsatz.
Beispiel 1
Wenn Sie schreiben
using Numbers = int[];
können Sie fortan Numbers anstelle von int[] bei Typdeklarationen verwenden:
Numbers numbers = new int[10];
Allerdings darf man den Alias NICHT bei der Instanziierung verwenden:
Numbers numbers = new Numbers;
Auch kann man leider keinen Alias definieren mit Hilfe eines Aliases. Das geht also auch nicht:
using DbIntList = List<DbInt>;
Zweites Beispiel: DbInt als Alias für ein int? bzw. Nullable<int>:
using DbInt = int?;
Danach ist möglich:
public DbInt LoadNumberFromDatabase()
{
try
{
…
}
catch (
Exception
)
{
return null;
}
}
DbInt n;
n = LoadNumberFromDatabase();
Console.WriteLine(n == null ? "null" : n);
Drittes Beispiel: Typalias für ein Tupel
using Measurement = (string Units, int Distance);
Danach ist möglich:
public Measurement Add(Measurement m1, Measurement m2)
{
if (m1.Units == m2.Units)
{
return (m1.Units, m1.Distance + m2.Distance);
}
else
{
throw new
Exception
("Units do not match!");
}
}
…
Measurement m1 = ("m", 100);
Console.WriteLine(m1.Distance + " " + m1.Units);
Measurement m2 = ("m", 42);
Console.WriteLine(m2.Distance + " " + m2.Units);
Measurement m3 = Add(m1, m2);
Console.WriteLine(m3);
Viertes Beispiel: Typalias für eine .NET-Klasse
using MyPerson = BO.Person;
Anders als beim Int-Array-Alias numbers ist hier eine Verwendung bei der Instanziierung gestattet:
MyPerson p = new MyPerson();
MyPerson[] pArray = new MyPerson[10];
Ein Typalias muss am Beginn einer Datei vor allen Typimplementierungen stehen. Der Typalias darf vor oder nach den using-Anweisungen für Namensraumimporte und vor oder nach der Namensraumdeklaration stehen.
Ausnahme
: Wenn der Typalias nicht nur für eine Datei, sondern alle Dateien im Projekt gelten soll, dann muss der Alias vor dem Namensraum stehen und zusätzlich das Schlüsselwort global besitzen. Ein Typalias kann nicht für andere Projekte exportiert werden. Er muss in jedem .NET-Projekt einmal deklariert sein, wenn er verwendet wird.
global using Measurement = (string Units, int Distance);
using BO;
namespace BL;
// Typaliase dürfen im Namensraum stehen
using Numbers = int[];
using DbInt = int?;
using MyPerson = Person;
class MeineKlasse
{
…
}
Optionale Lambda-Parameter in C#
12.0
Lambdas sind in den letzten Jahren an immer mehr Stellen vorgerückt, an denen zuvor
Methode
n geschrieben wurden. Allerdings erlaubten Lambdas bisher keine optionalen Parameter. Das hat sich in C# 12.0 geändert. Anstelle dieser Funktion mit optionalem Parameter z
public decimal Calc(decimal x, decimal y, decimal z = 1)
{
return (x + y) * z;
}
kann ein Entwickler in C# 12.0 nun auch diesen
Lambda-Ausdruck
schreiben:
var calc = (decimal x, decimal y, decimal z = 1) => (x + y) * z;
Das geht auch mit Statement Lambdas. Anstelle dieser
Methode
mit optionalem Parameter color
public void Print(object text, ConsoleColor? color = null)
{
if (color != null) Console.ForegroundColor = color.Value;
Console.WriteLine(text);
if (color != null) Console.ResetColor();
}
kann nun dieses Statement Lambda treten:
var Print = (object text, ConsoleColor? color = null) =>
{
if (color != null) Console.ForegroundColor = color.Value;
Console.WriteLine(text);
if (color != null) Console.ResetColor();
};
Parametermodifizierer ref readonly in C# 12.0
Seit C# 12.0 gibt es auch den Modifizierer ref readonly für
Methode
nparameter. Hierbei bekommt eine
Methode
einen Wert bzw. Objekt "by Reference" übergeben, darf den Wert bzw. das Objekt aber nicht ändern.
public string ParameterDemoValueTypes(int WertValue, in int WertIn, ref int WertRef, ref readonly int WertRefRO, out int WertOut)
{
WertValue++;
// nicht erlaubt, da in-Wert: WertIn++;
WertRef++;
// WertRefRO++; // nicht erlaubt, da readonly
// nicht erlaubt, da noch nicht initialisiert: WertOut++;
WertOut = 41;
return WertValue.ToString() + ";" + WertIn.ToString() + ";" + WertRef.ToString() + ";" + WertOut.ToString();
}
Hinweis: Wenn ein
Referenztyp
übergeben wird, kann die aufgerufene
Methode
immer die Daten im Objekt ändern. Die Modifizierer verhindern dann ggf. nur, dass ein anderes Objekt zugewiesen wird!
Erweiterter Einsatz von nameof() in C# 12.0
Der Operator nameof() funktionierte vor C# 12.0 in einigen Fällen nicht. Der Abruf des Namens von Instanzmitglieder von Klassenmitglieder war nicht möglich in einigen Fällen (z.B. statische Mitglieder,
Annotation
en) vor C# 12.0.
Beispiel: Name.Length funktioniert jetzt überall
public struct Person
{
public string Name;
// bisher schon möglich:
public string MemberName1() => nameof(Name);
// bisher schon möglich:
public string MemberName2() => nameof(Name.Length);
// bisher schon möglich:
public static string MemberName3() => nameof(Name);
// bisher Fehler CS0120:
public static string MemberName4() => nameof(Name.Length);
[Description($"{nameof(StringLength)} liefert von {nameof(Name)} die Eigenschaft {nameof(Name.Length)}")] // Name.Length nicht möglich vor C# 12.0!
public int StringLength()
{
return Name.Length;
}
}
Hier wäre vor C# 12.0 der Ausdruck nameof(Name.Length) in drei der vier Fällen nicht möglich gewesen und vom Compiler mit dem Kompilierungsfehler "error CS0120: An object reference is required for the non-static field, method, or property 'Name.Length'" quittiert worden.
Weitere Neuerungen in C# 12.0
Zwei weitere Neuerungen in C# 12.0 hat Microsoft primär für sich selbst für Optimierungen in der Entwicklung von Bibliotheken und dem Ahead-of-Timer-Compiler eingeführt:
Ein Interceptor (in C# 12.0 als experimentelles Feature enthalten) erlaubt, einen
Methode
naufruf abzufangen und umzulenken. Das will Microsoft vor allem einsetzen, um mehr Code kompatibel zum Ahead-of-Timer-Compiler zu machen (siehe dazu
https://devblogs.microsoft.com/dotnet/new-csharp-12-preview-features).
Zur Optimierung gibt es jetzt Inline Arrays (siehe dazu
https://devblogs.microsoft.com/dotnet/new-csharp-12-preview-features/).
Die Features können aber auch andere Entwickler im Rahmen der Low-Level-Programmierung verwenden. Sie sollen hier aber nicht erörtert werden, da sie wenig Relevanz für die meisten C#-Entwickler haben.
Querverweise zu anderen Begriffen im Lexikon
Optionale Lambda-Parameter in C#
Visual Studio for Mac
System.ComponentModel
Visual Studio Code (VSCode)
Visual Studio 2022 (VS17)
Lambda-Ausdruck
.NET Framework
Referenztyp
Objektmenge
Annotation
JavaScript (JS)
Decompiler
.NET Core
Vererbung
Exception
.NET 8.0 (.NET 8)
Variable
Ausnahme
Debugger
Prototyp
Methode
Xamarin
Literal
Typname
Field
Liste
CSharp 9.0 (C# 9.0)
CSharp 6.0 (C# 6.0)
Beratung & Support
Anfrage für Beratung/Consulting zu CSharp 12.0 C# 12.0
Gesamter Beratungsthemenkatalog
Technischer Support zum CSharp 12.0 C# 12.0
Schulungen zu diesem Thema
C# 13.0 - Neuerungen gegenüber C# 12.0
C# 12.0 - Neuerungen gegenüber C# 11.0
.NET 8.0 - Änderungen und Neuerungen gegenüber .NET 7.0
Umstieg auf .NET 8.0/9.0 - Entwicklerworkshop (Umstellung/Migration von klassischem .NET Framework zu .NET 6.0/8.0/9.0)
.NET-Entwickler-Update 2023
Anfrage für eine individuelle Schulung zum Thema CSharp 12.0 C# 12.0
Gesamter Schulungsthemenkatalog
Bücher zu diesem Thema
Blazor 8.0: Moderne Webanwendungen und hybride Cross-Platform-Apps mit .NET 8.0, C# 12.0 und Visual Studio 2022
C# 12.0 Crashkurs
Alle unsere aktuellen Fachbücher
E-Book-Abo für ab 99 Euro im Jahr