Kapitel 1. Einführung in C# und .NET
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
C# ist eine allgemeine, typsichere, objektorientierte Programmiersprache. Das Ziel der Sprache ist die Produktivität der Programmierer. Zu diesem Zweck bietet C# ein ausgewogenes Verhältnis zwischen Einfachheit, Ausdruckskraft und Leistung. Der Hauptarchitekt der Sprache seit ihrer ersten Version ist Anders Hejlsberg (Schöpfer von Turbo Pascal und Architekt von Delphi). Die Sprache C# ist plattformneutral und funktioniert mit einer Reihe von plattformspezifischen Laufzeiten.
Objektorientierung
C# ist eine reichhaltige Umsetzung des objektorientierten Paradigmas, das Kapselung, Vererbung und Polymorphismus umfasst. Verkapselung bedeutet, dass ein Objekt abgegrenzt wird, um sein externes (öffentliches) Verhalten von seinen internen (privaten) Implementierungsdetails zu trennen. Im Folgenden werden die besonderen Merkmale von C# aus objektorientierter Sicht beschrieben:
- Vereinheitlichtes Typensystem
- Der grundlegende Baustein in C# ist eine gekapselte Einheit von Daten und Funktionen, die als Typ bezeichnet wird. C# hat ein einheitliches Typensystem, in dem alle Typen letztlich einen gemeinsamen Basistyp haben. Das bedeutet, dass alle Typen, unabhängig davon, ob sie Geschäftsobjekte darstellen oder primitive Typen wie Zahlen sind, dieselbe Grundfunktionalität haben. Eine Instanz eines beliebigen Typs kann zum Beispiel durch den Aufruf der Methode
ToString
in eine Zeichenkette umgewandelt werden. - Klassen und Schnittstellen
- In einem traditionellen objektorientierten Paradigma ist die einzige Art von Typ eine Klasse. In C# gibt es mehrere andere Arten von Typen, darunter auch eine Schnittstelle. Eine Schnittstelle ist wie eine Klasse, die keine Daten enthalten kann. Das bedeutet, dass sie nur das Verhalten (und nicht den Zustand) definieren kann, was eine Mehrfachvererbung sowie eine Trennung zwischen Spezifikation und Implementierung ermöglicht.
- Eigenschaften, Methoden und Ereignisse
- Im rein objektorientierten Paradigma sind alle Funktionen Methoden. In C# sind Methoden nur eine Art von Funktionsmitgliedern, zu denen auch Eigenschaften und Ereignisse gehören (es gibt auch noch andere). Eigenschaften sind Funktionsmitglieder, die einen Teil des Zustands eines Objekts kapseln, z. B. die Farbe einer Schaltfläche oder den Text eines Etiketts. Ereignisse sind Funktionsmitglieder, die das Handeln bei Änderungen des Objektzustands vereinfachen.
Obwohl C# in erster Linie eine objektorientierte Sprache ist, nimmt sie auch Anleihen beim funktionalen Programmierparadigma, genauer gesagt:
- Funktionen können als Werte behandelt werden
- Mit Delegates können in C# Funktionen als Werte an und von anderen Funktionen übergeben werden.
- C# unterstützt Muster für Reinheit
- Der Kern der funktionalen Programmierung besteht darin, die Verwendung von Variablen, deren Werte sich ändern, zu vermeiden und stattdessen deklarative Muster zu verwenden. C# verfügt über wichtige Funktionen, die bei diesen Mustern helfen, z. B. die Möglichkeit, unbenannte Funktionen zu schreiben, die Variablen "einfangen"(Lambda-Ausdrücke), und die Möglichkeit, Listen- oder reaktive Programmierung über Abfrageausdrücke durchzuführen. C# bietet außerdem Datensätze, die das Schreiben unveränderlicher (schreibgeschützter) Typen erleichtern.
Typ Sicherheit
C# ist in erster Linie eine typensichere Sprache. Das bedeutet, dass Instanzen von Typen nur über die von ihnen definierten Protokolle interagieren können, wodurch die interne Konsistenz jedes Typs sichergestellt wird. C# verhindert zum Beispiel, dass du mit einem String-Typ interagieren kannst, als wäre er ein Integer-Typ.
Genauer gesagt unterstützt C# statische Typisierung, d.h. die Sprache erzwingt Typsicherheit zur Kompilierzeit. Dies geschieht zusätzlich zur Typsicherheit, die zur Laufzeit erzwungen wird.
Die statische Typisierung beseitigt eine große Anzahl von Fehlern, bevor ein Programm überhaupt ausgeführt wird. Sie verlagert die Last von den Unit-Tests zur Laufzeit auf den Compiler, um zu überprüfen, ob alle Typen in einem Programm richtig zusammenpassen. Das macht große Programme viel einfacher zu verwalten, vorhersehbarer und robuster. Außerdem hilft dir die statische Typisierung mit Tools wie IntelliSense in Visual Studio beim Schreiben eines Programms, weil es für eine bestimmte Variable weiß, welcher Typ sie ist und welche Methoden du daher für diese Variable aufrufen kannst. Solche Tools können auch überall in deinem Programm erkennen, wo eine Variable, ein Typ oder eine Methode verwendet wird, und ermöglichen so ein zuverlässiges Refactoring.
Hinweis
C# erlaubt es auch, Teile deines Codes mit dem Schlüsselwort dynamic
dynamisch zu typisieren. Dennoch bleibt C# eine überwiegend statisch typisierte Sprache.
C# wird auch als stark typisierte Sprache bezeichnet, weil die Typregeln streng durchgesetzt werden (entweder statisch oder zur Laufzeit). Du kannst zum Beispiel keine Funktion, die eine ganze Zahl akzeptiert, mit einer Fließkommazahl aufrufen, wenn du die Fließkommazahl nicht vorher explizit in eine ganze Zahl umwandelst. Das hilft, Fehler zu vermeiden.
Speicherverwaltung
C# verlässt sich bei der automatischen Speicherverwaltung auf die Laufzeitumgebung. Die Common Language Runtime verfügt über einen Garbage Collector, der als Teil deines Programms ausgeführt wird und den Speicher für Objekte, die nicht mehr referenziert werden, zurückfordert. Dadurch muss der Programmierer den Speicher für ein Objekt nicht mehr explizit freigeben, und das Problem der falschen Zeiger, das in Sprachen wie C++ auftritt, entfällt.
C# schafft Zeiger nicht ab: Es macht sie lediglich für die meisten Programmieraufgaben überflüssig. Für leistungsrelevante Hotspots und Interoperabilität sind Zeiger und explizite Speicherzuweisungen in Blöcken erlaubt, die mit unsafe
gekennzeichnet sind.
Plattform Unterstützung
C# hat Laufzeiten, die die folgenden Plattformen unterstützen:
Windows Desktop 7-11 (für Rich-Client-, Web-, Server- und Befehlszeilenanwendungen)
macOS (für Rich-Client-, Web- und Kommandozeilen-Anwendungen)
Linux und macOS (für Web- und Kommandozeilenanwendungen)
Android und iOS (für mobile Anwendungen)
Windows 10 Geräte (Xbox, Surface Hub und HoloLens)
Es gibt auch eine Technologie namens Blazor, die C# zu einer Web-Assembly kompilieren kann, die in einem Browser läuft.
CLRs, BCLs und Laufzeiten
Die Laufzeitunterstützung für C#-Programme besteht aus einer Common Language Runtime und einer Base Class Library. Eine Runtime kann auch eine übergeordnete Anwendungsschicht enthalten, die Bibliotheken für die Entwicklung von Rich-Client-, Mobil- oder Web-Anwendungen enthält (siehe Abbildung 1-1). Es gibt unterschiedliche Laufzeiten für verschiedene Arten von Anwendungen und verschiedene Plattformen.
Common Language Runtime
Eine Common Language Runtime (CLR) bietet wichtige Laufzeitdienste wie die automatische Speicherverwaltung und die Ausnahmebehandlung. (Das Wort "Common" bezieht sich auf die Tatsache, dass dieselbe Runtime auch von anderen verwalteten Programmiersprachen wie F#, Visual Basic und Managed C++ genutzt werden kann).
C# wird als verwaltete Sprache bezeichnet, weil es den Quellcode in verwalteten Code kompiliert, der in Intermediate Language (IL) dargestellt wird. Die CLR wandelt den IL-Code in den nativen Code der Maschine um, z. B. X64 oder X86, normalerweise kurz vor der Ausführung. Dies wird als Just-in-Time (JIT) Kompilierung bezeichnet. Die Ahead-of-Time-Kompilierung ist auch verfügbar, um die Startzeit bei großen Assemblies oder ressourcenbeschränkten Geräten zu verkürzen (und um die Regeln des iOS App Stores bei der Entwicklung mobiler Apps zu erfüllen).
Der Container für verwalteten Code wird Assembly genannt. Eine Assembly enthält nicht nur AWL, sondern auch Typinformationen(Metadaten). Durch das Vorhandensein von Metadaten können Assemblies auf Typen in anderen Assemblies verweisen, ohne dass zusätzliche Dateien benötigt werden.
Hinweis
Mit dem Tool ildasm von Microsoft kannst du den Inhalt einer Assembly untersuchen und disassemblieren. Und mit Tools wie ILSpy oder dotPeek von JetBrain kannst du noch weiter gehen und die IL in C# dekompilieren. Da IL eine höhere Ebene als nativer Maschinencode ist, kann der Decompiler das ursprüngliche C# ziemlich gut rekonstruieren.
Ein Programm kann seine eigenen Metadaten abfragen(reflection) und sogar neue IL zur Laufzeit erzeugen(reflection.emit).
Basisklassenbibliothek
Eine CLR wird immer mit einer Reihe von Assemblies ausgeliefert, die Base Class Library (BCL) genannt werden. Eine BCL stellt Programmierern Kernfunktionen zur Verfügung, z. B. Sammlungen, Ein-/Ausgabe, Textverarbeitung, XML/JSON-Verarbeitung, Netzwerke, Verschlüsselung, Interop, Parallelität und parallele Programmierung.
Eine BCL implementiert auch Typen, die die Sprache C# selbst benötigt (für Funktionen wie Aufzählungen, Abfragen und Asynchronität) und ermöglicht dir den expliziten Zugriff auf Funktionen der CLR, wie Reflection und Speicherverwaltung.
Laufzeiten
Eine Runtime (auch Framework genannt) ist eine einsatzfähige Einheit, die du herunterlädst und installierst. Eine Runtime besteht aus einer CLR (mit ihrer BCL) und einer optionalen Anwendungsschicht , die für die Art der Anwendung, die du schreibst, spezifisch ist - Web, Mobile, Rich Client usw. (Wenn du eine Kommandozeilen-Konsolenanwendung oder eine Nicht-UI-Bibliothek schreibst, brauchst du keine Anwendungsschicht).
Wenn du eine Anwendung schreibst, wählst du eine bestimmte Laufzeit aus. Das bedeutet, dass deine Anwendung die Funktionen nutzt, die die Laufzeit zur Verfügung stellt, und davon abhängig ist. Die Wahl der Laufzeit bestimmt auch, welche Plattformen deine Anwendung unterstützen wird.
In der folgenden Tabelle sind die wichtigsten Laufzeitoptionen aufgeführt:
Anwendungsschicht | CLR/BCL | Programmtyp | Läuft auf... |
---|---|---|---|
ASP.NET | .NET 6 | Web | Windows, Linux, macOS |
Windows-Desktop | .NET 6 | Windows | Windows 7-10+ |
MAUI (Anfang 2022) | .NET 6 | Mobil, Desktop | iOS, Android, macOS, Windows 10+ |
WinUI 3 (Anfang 2022) | .NET 6 | Win10 | Windows 10+ Desktop |
UWP | .NET Core 2.2 | Win10 + Win10 Geräte | Windows 10+ Desktop & Geräte |
(Veraltetes) .NET Framework | .NET Framework | Web, Windows | Windows 7-10+ |
Abbildung 1-2 zeigt diese Informationen grafisch und dient auch als Leitfaden für die Inhalte des Buches.
.NET 6
.NET 6 ist das Flaggschiff der Open-Source-Laufzeitumgebung von Microsoft. Du kannst Web- und Konsolenanwendungen schreiben, die unter Windows, Linux und macOS laufen; Rich-Client-Anwendungen, die unter Windows 7 bis 11 und macOS laufen; und mobile Apps, die unter iOS und Android laufen. Dieses Buch konzentriert sich auf die .NET 6 CLR und BCL.
Anders als .NET Framework ist .NET 6 auf Windows-Rechnern nicht vorinstalliert. Wenn du versuchst, eine .NET 6-Anwendung auszuführen, ohne dass die richtige Runtime vorhanden ist, wird eine Meldung angezeigt, die dich auf eine Webseite verweist, von der du die Runtime herunterladen kannst. Du kannst dies vermeiden, indem du eine eigenständige Bereitstellung erstellst, die die für die Anwendung erforderlichen Teile der Laufzeitumgebung enthält.
Hinweis
Der Vorgänger von .NET 6 war .NET 5, dessen Vorgänger .NET Core 3 war. (Microsoft hat "Core" aus dem Namen entfernt und Version 4 übersprungen.) Der Grund für das Überspringen einer Version war, Verwechslungen mit .NET Framework 4.x zu vermeiden.
Das bedeutet, dass Assemblies, die unter den .NET Core-Versionen 1, 2 und 3 (und .NET 5) kompiliert wurden, in den meisten Fällen ohne Änderungen unter .NET 6 laufen. Im Gegensatz dazu sind Assemblies, die unter (einer beliebigen Version von) .NET Framework kompiliert wurden, in der Regel nicht mit .NET 6 kompatibel.
Die BCL und CLR von .NET 6 sind .NET 5 (und .NET Core 3) sehr ähnlich, wobei sich die Unterschiede hauptsächlich auf die Leistung und die Bereitstellung konzentrieren.
MAUI
MAUI (Multi-platform App UI, Anfang 2022) wurde für die Entwicklung von mobilen Apps für iOS und Android sowie von plattformübergreifenden Desktop-Apps für macOS und Windows entwickelt. MAUI ist eine Weiterentwicklung von Xamarin und ermöglicht es, mit einem einzigen Projekt mehrere Plattformen zu bedienen.
UWP und WinUI 3
DieUniversal Windows Platform (UWP) wurde entwickelt, um immersive Touch-First-Anwendungen zu schreiben, die auf Windows 10+ Desktop und Geräten (Xbox, Surface Hub und HoloLens) laufen. UWP-Apps sind sandboxed und werden über den Windows Store ausgeliefert. UWP ist bei Windows 10 vorinstalliert. Sie nutzt eine Version der .NET Core 2.2 CLR/BCL, und es ist unwahrscheinlich, dass diese Abhängigkeit aktualisiert wird. Stattdessen hat Microsoft einen Nachfolger namens WinUI 3 als Teil des Windows App SDK veröffentlicht.
Das Windows App SDK arbeitet mit dem neuesten .NET, integriert sich besser in die .NET-Desktop-APIs und kann außerhalb einer Sandbox ausgeführt werden. Allerdings unterstützt es noch keine Geräte wie die Xbox oder die HoloLens.
.NET Framework
.NET Framework ist Microsofts ursprüngliche Windows-Laufzeitumgebung zum Schreiben von Web- und Rich-Client-Anwendungen, die (nur) auf Windows-Desktops/Servern laufen. Es sind keine größeren neuen Versionen geplant, obwohl Microsoft die aktuelle Version 4.8 aufgrund der Fülle der bestehenden Anwendungen weiterhin unterstützen und pflegen wird.
Mit dem .NET Framework wird die CLR/BCL in die Anwendungsschicht integriert. In .NET Framework geschriebene Anwendungen können unter .NET 6 neu kompiliert werden, obwohl sie in der Regel einige Änderungen erfordern. Einige Funktionen von .NET Framework sind in .NET 6 nicht vorhanden (und umgekehrt).
Das .NET Framework ist bei Windows vorinstalliert und wird automatisch über Windows Update gepatcht. Wenn du auf .NET Framework 4.8 abzielst, kannst du die Funktionen von C# 7.3 und früher nutzen.
Hinweis
Das Wort ".NET" wird seit langem als Oberbegriff für alle Technologien verwendet, die das Wort ".NET" enthalten (.NET Framework, .NET Core, .NET Standard usw.).
Das bedeutet, dass Microsofts Umbenennung von .NET Core in .NET eine unglückliche Zweideutigkeit geschaffen hat. In diesem Buch werden wir das neue .NET als .NET 5+ bezeichnen. Und um auf .NET Core und seine Nachfolger zu verweisen, verwenden wir den Ausdruck ".NET Core und .NET 5+".
Um die Verwirrung noch zu vergrößern, ist .NET (5+) ein Framework, das sich jedoch stark vom .NET Framework unterscheidet. Daher verwenden wir, wenn möglich, den Begriff " Runtime" statt " Framework".
Nischen-Laufzeiten
Außerdem gibt es die folgenden Nischenlaufzeiten:
Das .NET Micro Framework ist für die Ausführung von .NET-Code auf stark ressourcenbeschränkten eingebetteten Geräten (unter einem Megabyte) gedacht.
Unity ist eine Spieleentwicklungsplattform, die es ermöglicht, Spiellogik mit C# zu skripten.
Es ist auch möglich, verwalteten Code in SQL Server auszuführen. Mit der SQL Server CLR-Integration kannst du benutzerdefinierte Funktionen, Stored Procedures und Aggregationen in C# schreiben und sie dann von SQL aus aufrufen. Dies funktioniert in Verbindung mit dem .NET Framework und einer speziellen "gehosteten" CLR, die eine Sandbox erzwingt, um die Integrität des SQL Server-Prozesses zu schützen.
Eine kurze Geschichte von C#
Im Folgenden findest du eine umgekehrte Chronologie der neuen Funktionen in jeder C#-Version, damit auch Leser, die bereits mit einer älteren Version der Sprache vertraut sind, davon profitieren können.
Was ist neu in C# 10?
C# 10 ist im Lieferumfang von Visual Studio 2022 enthalten und wird verwendet, wenn du auf .NET 6 abzielst.
Namensräume mit Dateischutz
In dem häufigen Fall, dass alle Typen in einer Datei in einem einzigen Namensraum definiert sind, reduziert eine dateiübergreifende Namensraumdeklaration in C# 10 das Durcheinander und eliminiert eine unnötige Einrückungsebene:
namespace MyNamespace; // Applies to everything that follows in the file. class Class1 {} // inside MyNamespace class Class2 {} // inside MyNamespace
Die globale using-Direktive
Wenn du einer using
Direktive das Schlüsselwort global
voranstellst, gilt die Direktive für alle Dateien im Projekt:
global using System; global using System.Collection.Generic;
So vermeidest du, dass du in jeder Datei dieselben Direktiven wiederholst. global using
Direktiven funktionieren mit using static
.
Außerdem unterstützen .NET 6-Projekte jetzt implizite globale using-Direktiven: Wenn das Element ImplicitUsings
in der Projektdatei auf true gesetzt ist, werden die am häufigsten verwendeten Namensräume automatisch importiert (basierend auf dem SDK-Projekttyp). Siehe "Die globale using-Direktive (C# 10)" für weitere Details.
Nicht-destruktive Mutation für anonyme Typen
In C# 9 wurde das Schlüsselwort with
eingeführt, um eine nicht-destruktive Mutation von Datensätzen durchzuführen. In C# 10 funktioniert das Schlüsselwort with
auch mit anonymen Typen:
var a1 = new { A = 1, B = 2, C = 3, D = 4, E = 5 }; var a2 = a1 with { E = 10 }; Console.WriteLine (a2); // { A = 1, B = 2, C = 3, D = 4, E = 10 }
Neue Dekonstruktionssyntax
Mit C# 7 wurde die Dekonstruktionssyntax für Tupel (oder jeden Typ mit einer Deconstruct
Methode). C# 10 geht noch einen Schritt weiter und erlaubt es dir, Zuweisung und Deklaration in derselben Dekonstruktion zu kombinieren:
var point = (3, 4); double x = 0; (x, double y) = point;
Feldinitialisierungen und parameterlose Konstruktoren in structs
Ab C# 10 kannst du Feldinitialisierungen und parameterlose Konstruktoren in Structs einfügen (siehe "Structs"). Diese werden nur ausgeführt, wenn der Konstruktor explizit aufgerufen wird, und können daher leicht umgangen werden - zum Beispiel mit dem Schlüsselwort default
. Diese Funktion wurde vor allem zum Vorteil von struct-Records eingeführt.
Datensatz-Strukturen
Datensätze wurden erstmals in C# 9 eingeführt, wo sie als kompilierte, erweiterte Klasse fungierten. In C# 10 können Datensätze auch Structs sein:
record struct Point (int X, int Y);
Die Regeln sind ansonsten ähnlich: Record-Strukturen haben fast die gleichen Eigenschaften wie Klassen-Strukturen (siehe "Records"). Eine Ausnahme ist, dass die vom Compiler erzeugten Eigenschaften von Record-Strukturen beschreibbar sind, es sei denn, du stellst der Record-Deklaration das Schlüsselwort readonly
voran.
Erweiterungen des Lambda-Ausdrucks
Die Syntax von Lambda-Ausdrücken wurde in mehrfacher Hinsicht verbessert. Erstens ist die implizite Typisierung (var
) erlaubt:
var greeter = () => "Hello, world";
Der implizite Typ für einen Lambda-Ausdruck ist ein Action
oder Func
Delegat, also ist greeter
in diesem Fall vom Typ Func<string>
. Du musst alle Parametertypen explizit angeben:
var square = (int x) => x * x;
Zweitens kann ein Lambda-Ausdruck einen Rückgabetyp angeben:
var sqr = int (int x) => x;
Dies dient in erster Linie dazu, die Compilerleistung bei komplexen verschachtelten Lambdas zu verbessern.
Drittens kannst du einen Lambda-Ausdruck an einen Methodenparameter vom Typ object
, Delegate,
oder Expression
übergeben:
M1 (() => "test"); // Implicitly typed to Func<string> M2 (() => "test"); // Implicitly typed to Func<string> M3 (() => "test"); // Implicitly typed to Expression<Func<string>> void M1 (object x) {} void M2 (Delegate x) {} void M3 (Expression x) {}
Schließlich kannst du Attribute auf die vom Kompilierer erzeugte Zielmethode eines Lambda-Ausdrucks anwenden (sowie auf ihre Parameter und ihren Rückgabewert):
Action a = [Description("test")] () => { };
Siehe "Attribute auf Lambda-Ausdrücke anwenden (C# 10)" für weitere Details.
Verschachtelte Eigenschaftsmuster
Die folgende vereinfachte Syntax ist in C# 10 für den Abgleich verschachtelter Eigenschaftsmuster zulässig (siehe "Eigenschaftsmuster"):
var obj = new Uri ("https://www.linqpad.net"); if (obj is Uri { Scheme.Length: 5 }) ...
Dies ist gleichbedeutend mit:
if (obj is Uri { Scheme: { Length: 5 }}) ...
CallerArgumentExpression
Ein Methodenparameter, auf den du das Attribut [CallerArgumentExpression]
anwendest, fängt einen Argumentausdruck von der Aufrufseite ein:
Print (Math.PI * 2); void Print (double number, [CallerArgumentExpression("number")] string expr = null) => Console.WriteLine (expr); // Output: Math.PI * 2
Diese Funktion ist vor allem für Validierungs- und Assertion-Bibliotheken gedacht (siehe "CallerArgumentExpression (C# 10)").
Andere neue Funktionen
Die #line
Direktive wurde in C# 10 erweitert, damit eine Spalte und ein Bereich angegeben werden können.
Interpolierte Strings in C# 10 können Konstanten sein, solange die interpolierten Werte Konstanten sind.
Datensätze können die Methode ToString()
in C# 10 versiegeln.
Die definitive Zuweisungsanalyse von C# wurde verbessert, damit Ausdrücke wie der folgende funktionieren:
if (foo?.TryParse ("123", out var number) ?? false) Console.WriteLine (number);
(Vor C# 10 hat der Compiler eine Fehlermeldung ausgegeben: "Verwendung einer nicht zugewiesenen lokalen Variable 'number'.")
Was ist neu in C# 9.0
C# 9.0 wird mit Visual Studio 2019 ausgeliefert und wird verwendet, wenn du auf .NET 5 abzielst.
Top-Level-Anweisungen
Mit Top-Level-Anweisungen (siehe "Top-Level-Anweisungen") kannst du ein Programm ohne den Ballast einer Main
Methode und Program
Klasse schreiben:
using System; Console.WriteLine ("Hello, world");
Top-Level-Anweisungen können Methoden enthalten (die sich wie lokale Methoden verhalten). Sie können auch über die "magische" Variable args
auf Kommandozeilenargumente zugreifen und einen Wert an den Aufrufer zurückgeben. Auf Top-Level-Anweisungen können Typ- und Namensraumdeklarationen folgen.
Init-Only-Setter
Ein Init-Only-Setter (siehe "Init-Only-Setter") in einer Eigenschaftsdeklaration verwendet das Schlüsselwort init
anstelle des Schlüsselworts set
:
class Foo { public int ID { get; init; } }
Diese Eigenschaft verhält sich wie eine schreibgeschützte Eigenschaft, außer dass sie auch über einen Objektinitialisierer gesetzt werden kann:
var foo = new Foo { ID = 123 };
Dies ermöglicht es, unveränderliche (schreibgeschützte) Typen zu erstellen, die über einen Objektinitialisierer statt über einen Konstruktor gefüllt werden können, und hilft, das Antipattern von Konstruktoren zu vermeiden, die eine große Anzahl optionaler Parameter akzeptieren. Init-only Setter ermöglichen auch eine nicht-destruktive Mutation, wenn sie in Datensätzen verwendet werden.
Aufzeichnungen
Ein Datensatz (siehe "Datensätze") ist eine besondere Art von Klasse, die für unveränderliche Daten ausgelegt ist. Das Besondere an ihr ist, dass sie eine nicht-destruktive Mutation über ein neues Schlüsselwort (with
) unterstützt:
Point p1 = new Point (2, 3); Point p2 = p1 with { Y = 4 }; // p2 is a copy of p1, but with Y set to 4 Console.WriteLine (p2); // Point { X = 2, Y = 4 } record Point { public Point (double x, double y) => (X, Y) = (x, y); public double X { get; init; } public double Y { get; init; } }
In einfachen Fällen kann ein Datensatz auch die Definition von Eigenschaften und das Schreiben eines Konstruktors und Dekonstruktors überflüssig machen. Wir können unsere Point
Datensatzdefinition durch die folgende ersetzen, ohne dass die Funktionalität darunter leidet:
record Point (double X, double Y);
Wie Tupel haben auch Datensätze standardmäßig strukturelle Gleichheit. Datensätze können Unterklassen anderer Datensätze sein und dieselben Konstrukte enthalten, die auch Klassen enthalten können. Der Compiler implementiert Datensätze zur Laufzeit als Klassen.
Verbesserungen beim Pattern-Matching
Mit dem Beziehungsmuster (siehe "Muster") können die Operatoren <
, >
, <=
und >=
in Mustern erscheinen:
string GetWeightCategory (decimal bmi) => bmi switch { < 18.5m => "underweight", < 25m => "normal", < 30m => "overweight", _ => "obese" };
Mit Musterkombinatoren kannst du Muster über drei neue Schlüsselwörter (and
, or
und not
) kombinieren:
bool IsVowel (char c) => c is 'a' or 'e' or 'i' or 'o' or 'u'; bool IsLetter (char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Wie bei den Operatoren &&
und ||
hat auch and
einen höheren Vorrang als or
. Du kannst dies mit Klammern außer Kraft setzen.
Der not
Kombinator kann zusammen mit dem Typmuster verwendet werden, um zu prüfen, ob ein Objekt ein Typ ist (oder nicht):
if (obj is not string) ...
Neue Ausdrücke vom Typ "Ziel
Wenn du ein Objekt konstruierst, kannst du in C# 9 den Typnamen weglassen, wenn der Compiler ihn eindeutig ableiten kann:
System.Text.StringBuilder sb1 = new(); System.Text.StringBuilder sb2 = new ("Test");
Das ist besonders nützlich, wenn die Variablendeklaration und die Initialisierung in verschiedenen Teilen deines Codes liegen:
class Foo { System.Text.StringBuilder sb; public Foo (string initialValue) => sb = new (initialValue); }
Und im folgenden Szenario:
MyMethod (new ("test")); void MyMethod (System.Text.StringBuilder sb) { ... }
Weitere Informationen findest du unter "Zielgerichtete neue Ausdrücke".
Interop-Verbesserungen
Mit C# 9 wurden Funktionszeiger eingeführt (siehe "Funktionszeiger" und "Rückrufe mit Funktionszeigern"). Ihr Hauptzweck ist es, nicht verwalteten Code zu ermöglichen, statische Methoden in C# ohne den Overhead einer Delegateninstanz aufzurufen. Dabei kann die P/Invoke-Schicht umgangen werden, wenn die Argumente und Rückgabetypen blittable sind (auf beiden Seiten identisch dargestellt).
Mit C# 9 wurden auch die Integer-Typen nint
und nuint
eingeführt (siehe "Native-Sized Integers"), die zur Laufzeit auf System.IntPtr
und System.UIntPtr
. Zur Kompilierzeit verhalten sie sich wie numerische Typen mit Unterstützung für arithmetische Operationen.
Andere neue Funktionen
Außerdem kannst du mit C# 9 jetzt Folgendes tun:
Überschreibe eine Methode oder schreibgeschützte Eigenschaft so, dass sie einen abgeleiteten Typ zurückgibt (siehe "Kovariante Rückgabetypen")
Attribute auf lokale Funktionen anwenden (siehe "Attribute")
Wende das Schlüsselwort
static
auf Lambda-Ausdrücke oder lokale Funktionen an, um sicherzustellen, dass du nicht versehentlich lokale oder Instanzvariablen erfasst (siehe "Statische Lambdas")Du kannst jeden Typ mit der
foreach
Anweisung verwenden, indem du eineGetEnumerator
Erweiterungsmethode schreibst.Definiere eine Modulinitialisierungsmethode, die einmal ausgeführt wird, wenn eine Assembly zum ersten Mal geladen wird, indem du das
[ModuleInitializer]
Attribut auf eine (static void parameterless) Methode anwendestVerwirf" (Unterstrich) als Argument für einen Lambda-Ausdruck verwenden
Schreibe erweiterte partielle Methoden, die zwingend erforderlich sind, um Szenarien wie die neuen Quellgeneratoren von Roslyn zu implementieren (siehe "Erweiterte partielle Methoden")
ein Attribut auf Methoden, Typen oder Module anwenden, um zu verhindern, dass lokale Variablen von der Laufzeit initialisiert werden (siehe "[SkipLocalsInit]")
Was ist neu in C# 8.0
C# 8.0 wurde erstmals mit Visual Studio 2019 ausgeliefert und wird auch heute noch verwendet, wenn du .NET Core 3 oder .NET Standard 2.1 anvisierst.
Indizes und Bereiche
Indizes und Bereiche vereinfachen die Arbeit mit Elementen oder Teilen eines Arrays (oder den Low-Level-Typen Span<T>
und ReadOnlySpan<T>
).
Mit Indizes kannst du dich auf Elemente relativ zum Ende eines Arrays beziehen, indem du den ^
Operator verwendest. ^1
bezieht sich auf das letzte Element, ^2
auf das vorletzte Element und so weiter:
char[] vowels = new char[] {'a','e','i','o','u'}; char lastElement = vowels [^1]; // 'u' char secondToLast = vowels [^2]; // 'o'
Mit Ranges kannst du ein Array mit Hilfe des ..
Operators "zerschneiden":
char[] firstTwo = vowels [..2]; // 'a', 'e' char[] lastThree = vowels [2..]; // 'i', 'o', 'u' char[] middleOne = vowels [2..3] // 'i' char[] lastTwo = vowels [^2..]; // 'o', 'u'
C# implementiert Indizes und Bereiche mit Hilfe der Typen Index
und Range
:
Index last = ^1; Range firstTwoRange = 0..2; char[] firstTwo = vowels [firstTwoRange]; // 'a', 'e'
Du kannst Indizes und Bereiche in deinen eigenen Klassen unterstützen, indem du einen Indexer mit einem Parametertyp von Index
oder Range
definierst:
class Sentence { string[] words = "The quick brown fox".Split(); public string this [Index index] => words [index]; public string[] this [Range range] => words [range]; }
Weitere Informationen findest du unter "Indizes".
Null-Koaleszenz-Zuweisung
Der ??=
Operator weist eine Variable nur zu, wenn sie null ist. Anstelle von
if (s == null) s = "Hello, world";
kannst du das jetzt schreiben:
s ??= "Hello, world";
Deklarationen verwenden
Wenn du die Klammern und den Anweisungsblock nach einer using
Anweisung weglässt, wird sie zu einer using-Deklaration. Die Ressource wird dann entsorgt, wenn die Ausführung außerhalb des einschließenden Anweisungsblocks erfolgt:
if (File.Exists ("file.txt")) { using var reader = File.OpenText ("file.txt"); Console.WriteLine (reader.ReadLine()); ... }
In diesem Fall wird reader
entsorgt, wenn die Ausführung außerhalb des if
Anweisungsblocks erfolgt.
Schreibgeschützte Mitglieder
In C# 8 kannst du den readonly
Modifikator auf die Funktionen einer Struktur anwenden, um sicherzustellen, dass ein Kompilierfehler erzeugt wird, wenn die Funktion versucht, ein Feld zu ändern:
struct Point { public int X, Y; public readonly void ResetX() => X = 0; // Error! }
Wenn eine readonly
Funktion eine nichtreadonly
Funktion aufruft, erzeugt der Compiler eine Warnung (und kopiert defensiv die Struktur, um die Möglichkeit einer Mutation zu vermeiden).
Statische lokale Methoden
Wenn du einer lokalen Methode den Modifikator static
hinzufügst, kann sie die lokalen Variablen und Parameter der umschließenden Methode nicht sehen. Dies trägt dazu bei, die Kopplung zu verringern, und ermöglicht es der lokalen Methode, Variablen nach Belieben zu deklarieren, ohne Gefahr zu laufen, mit den Variablen der einschließenden Methode zu kollidieren.
Standardschnittstellenmitglieder
In C# 8 kannst du eine Standardimplementierung zu einem Schnittstellenmitglied hinzufügen, sodass die Implementierung optional ist:
interface ILogger { void Log (string text) => Console.WriteLine (text); }
Das bedeutet, dass du ein Mitglied zu einer Schnittstelle hinzufügen kannst, ohne Implementierungen zu zerstören. Standardimplementierungen müssen explizit über die Schnittstelle aufgerufen werden:
((ILogger)new Logger()).Log ("message");
Interfaces können auch statische Mitglieder (einschließlich Felder) definieren, auf die von Code innerhalb von Standardimplementierungen zugegriffen werden kann:
interface ILogger { void Log (string text) => Console.WriteLine (Prefix + text); static string Prefix = ""; }
Oder von außerhalb der Schnittstelle, es sei denn, die Erreichbarkeit wird durch einen Modifikator für das statische Schnittstellenmitglied eingeschränkt (z. B. private
, protected
oder internal
):
ILogger.Prefix = "File log: ";
Instanzfelder sind verboten. Weitere Informationen findest du unter "Standardschnittstellenmitglieder".
Ausdrücke wechseln
Ab C# 8 kannst du switch
im Kontext eines Ausdrucks verwenden:
string cardName = cardNumber switch // assuming cardNumber is an int { 13 => "King", 12 => "Queen", 11 => "Jack", _ => "Pip card" // equivalent to 'default' };
Weitere Beispiele findest du unter "Ausdrücke wechseln".
Tupel-, Positions- und Eigenschaftsmuster
C# 8 unterstützt drei neue Muster, die vor allem für Switch-Anweisungen/-ausdrücke von Vorteil sind (siehe "Muster"). Mit Tupel-Mustern kannst du auf mehrere Werte umschalten:
int cardNumber = 12; string suite = "spades"; string cardName = (cardNumber, suite) switch { (13, "spades") => "King of spades", (13, "clubs") => "King of clubs", ... };
Positionsmuster ermöglichen eine ähnliche Syntax für Objekte, die einen Dekonstruktor aufweisen, und Eigenschaftsmuster ermöglichen eine Übereinstimmung mit den Eigenschaften eines Objekts. Du kannst alle Muster sowohl in Schaltern als auch mit dem Operator is
verwenden. Im folgenden Beispiel wird ein Eigenschaftsmuster verwendet, um zu prüfen, ob obj
eine Zeichenkette mit der Länge 4 ist:
if (obj is string { Length:4 }) ...
Nullbare Referenztypen
Während nullbare Werttypen Werttypen nullbar machen, bewirken nullbare Referenztypen das Gegenteil und machen Referenztypen (bis zu einem gewissen Grad) nicht-nullbar, um NullReferenceException
s zu vermeiden. Nullbare Referenztypen führen eine Sicherheitsebene ein, die ausschließlich vom Compiler in Form von Warnungen oder Fehlern durchgesetzt wird, wenn er Code erkennt, der Gefahr läuft, eine NullReferenceException
.
Nullbare Referenztypen können entweder auf Projektebene (über das Element Nullable
in der Projektdatei .csproj ) oder im Code (über die Direktive #nullable
) aktiviert werden. Nach der Aktivierung macht der Compiler die Nichtnullbarkeit zum Standard: Wenn du willst, dass ein Referenztyp Nullen akzeptiert, musst du das Suffix ?
hinzufügen, um einen nullbaren Referenztyp anzugeben:
#nullable enable // Enable nullable reference types from this point on string s1 = null; // Generates a compiler warning! (s1 is non-nullable) string? s2 = null; // OK: s2 is nullable reference type
Uninitialisierte Felder erzeugen ebenfalls eine Warnung (wenn der Typ nicht als nullable markiert ist), ebenso wie die Dereferenzierung eines nullable Referenztyps, wenn der Compiler glaubt, dass ein NullReferenceException
auftreten könnte:
void Foo (string? s) => Console.Write (s.Length); // Warning (.Length)
Um die Warnung zu entfernen, kannst du den Null-Forgiving-Operator (!
) verwenden:
void Foo (string? s) => Console.Write (s!.Length);
Eine ausführliche Diskussion findest du unter "Nullable Reference Types".
Asynchrone Ströme
Vor C# 8 konntest du yield return
verwenden, um einen Iterator zu schreiben, oder await
, um eine asynchrone Funktion zu schreiben. Aber du konntest nicht beides tun und einen Iterator schreiben, der auf Elemente wartet und diese asynchron ausgibt. C# 8 behebt dieses Problem durch die Einführung von asynchronen Streams:
async IAsyncEnumerable<int> RangeAsync ( int start, int count, int delay) { for (int i = start; i < start + count; i++) { await Task.Delay (delay); yield return i; } }
Die Anweisung await foreach
konsumiert einen asynchronen Stream:
await foreach (var number in RangeAsync (0, 10, 100)) Console.WriteLine (number);
Weitere Informationen findest du unter "Asynchrone Streams".
Was ist neu in C# 7.x
C# 7.x wurde erstmals mit Visual Studio 2017 ausgeliefert. C# 7.3 wird auch heute noch von Visual Studio 2019 verwendet, wenn du .NET Core 2, .NET Framework 4.6 bis 4.8 oder .NET Standard 2.0 anvisierst.
C# 7.3
In C# 7.3 wurden kleinere Verbesserungen an bestehenden Funktionen vorgenommen, wie z.B. die Verwendung von Gleichheitsoperatoren mit Tupeln, die Verbesserung der Überladungsauflösung und die Möglichkeit, Attribute auf die hinteren Felder von automatischen Eigenschaften anzuwenden:
[field:NonSerialized] public int MyProperty { get; set; }
C# 7.3 baute auch auf den fortschrittlichen Funktionen von C# 7.2 für die Programmierung mit geringer Belegung auf, wie z.B. der Möglichkeit, ref locals neu zuzuweisen, dem Verzicht auf Pin beim Indizieren von fixed
Feldern und der Unterstützung von Feldinitialisierungen mit stackalloc
:
int* pointer = stackalloc int[] {1, 2, 3}; Span<int> arr = stackalloc [] {1, 2, 3};
Beachte, dass Stack-Speicher direkt einem Span<T>
zugewiesen werden kann. Wir beschreiben Spans - und warum du sie verwenden solltest - in Kapitel 23.
C# 7.2
C# 7.2 fügte einen neuen private protected
Modifikator (die Schnittmenge von internal
und protected
), die Möglichkeit, benannte Argumente beim Methodenaufruf durch Positionsargumente zu ersetzen, und readonly
structs hinzu. Eine readonly
struct erzwingt, dass alle Felder readonly
sind, um die Deklaration der Absicht zu erleichtern und dem Compiler mehr Optimierungsmöglichkeiten zu geben:
readonly struct Point { public readonly int X, Y; // X and Y must be readonly }
Mit C# 7.2 wurden außerdem spezielle Funktionen hinzugefügt, die bei der Mikro-Optimierung und der Programmierung mit geringem Speicherplatzbedarf helfen: siehe "Der in-Modifikator", "Ref Locals", "Ref Returns" und "Ref Structs").
C# 7.1
Ab C# 7.1 kannst du den Typ weglassen, wenn du das Schlüsselwort default
verwendest, wenn der Typ abgeleitet werden kann:
decimal number = default; // number is decimal
In C# 7.1 wurden auch die Regeln für Switch-Anweisungen gelockert (so dass du Pattern-Match auf generische Typparameter anwenden kannst), die Methode Main
eines Programms kann asynchron sein und die Namen von Tupel-Elementen können abgeleitet werden:
var now = DateTime.Now; var tuple = (now.Hour, now.Minute, now.Second);
Numerische wörtliche Verbesserungen
Numerische Literale in C# 7 können Unterstriche enthalten, um die Lesbarkeit zu verbessern. Diese werden als Zifferntrennzeichen bezeichnet und vom Compiler ignoriert:
int million = 1_000_000;
Binäre Literale können mit dem Präfix 0b
angegeben werden:
var b = 0b1010_1011_1100_1101_1110_1111;
Out-Variablen und Rückwürfe
C# 7 macht es einfacher, Methoden aufzurufen, die out
Parameter enthalten. Erstens kannst du jetzt spontan Out-Variablen deklarieren (siehe "Out-Variablen und Verwerfungen"):
bool successful = int.TryParse ("123", out int result); Console.WriteLine (result);
Und wenn du eine Methode mit mehreren out
Parametern aufrufst, kannst du diejenigen, die dich nicht interessieren, mit dem Unterstrich verwerfen:
SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _); Console.WriteLine (x);
Typmuster und Mustervariablen
Du kannst auch Variablen mit dem is
Operator einführen. Diese werden Mustervariablen genannt (siehe "Einführen einer Mustervariable"):
void Foo (object x) { if (x is string s) Console.WriteLine (s.Length); }
Die Anweisung switch
unterstützt auch Typmuster, so dass du nicht nur auf Konstanten, sondern auch auf Typen schalten kannst (siehe "Auf Typen schalten"). Du kannst Bedingungen mit einer when
Klausel angeben und auch den null
Wert einschalten:
switch (x) { case int i: Console.WriteLine ("It's an int!"); break; case string s: Console.WriteLine (s.Length); // We can use the s variable break; case bool b when b == true: // Matches only when b is true Console.WriteLine ("True"); break; case null: Console.WriteLine ("Nothing"); break; }
Lokale Methoden
Eine lokale Methode ist eine Methode, die innerhalb einer anderen Funktion deklariert wird (siehe "Lokale Methoden"):
void WriteCubes() { Console.WriteLine (Cube (3)); Console.WriteLine (Cube (4)); Console.WriteLine (Cube (5)); int Cube (int value) => value * value * value; }
Lokale Methoden sind nur für die enthaltende Funktion sichtbar und können lokale Variablen auf die gleiche Weise erfassen wie Lambda-Ausdrücke.
Mehr ausdrucksstarke Mitglieder
Mit C# 6 wurde die "Fat-Arrow"-Syntax für Methoden, schreibgeschützte Eigenschaften, Operatoren und Indexer eingeführt. C# 7 erweitert diese Syntax auf Konstruktoren, Lese-/Schreibeigenschaften und Finalizer:
public class Person { string name; public Person (string name) => Name = name; public string Name { get => name; set => name = value ?? ""; } ~Person () => Console.WriteLine ("finalize"); }
Dekonstrukteure
C# 7 führt das Dekonstruktor-Muster ein (siehe "Dekonstruktoren"). Während ein Konstruktor normalerweise eine Reihe von Werten (als Parameter) entgegennimmt und sie Feldern zuweist, macht ein Dekonstruktor das Gegenteil und weist Felder wieder einer Reihe von Variablen zu. Wir könnten einen Dekonstruktor für die Klasse Person
aus dem vorangegangenen Beispiel wie folgt schreiben (abgesehen von der Ausnahmebehandlung):
public void Deconstruct (out string firstName, out string lastName) { int spacePos = name.IndexOf (' '); firstName = name.Substring (0, spacePos); lastName = name.Substring (spacePos + 1); }
Dekonstrukteure werden mit der folgenden speziellen Syntax aufgerufen:
var joe = new Person ("Joe Bloggs"); var (first, last) = joe; // Deconstruction Console.WriteLine (first); // Joe Console.WriteLine (last); // Bloggs
Tupel
Die vielleicht bemerkenswerteste Verbesserung in C# 7 ist die explizite Unterstützung von Tupeln (siehe "Tupel"). Tupel bieten eine einfache Möglichkeit, eine Reihe von zusammenhängenden Werten zu speichern:
var bob = ("Bob", 23); Console.WriteLine (bob.Item1); // Bob Console.WriteLine (bob.Item2); // 23
Die neuen Tupel in C# sind ein syntaktischer Zucker für die Verwendung von System.ValueTuple<…>
generic structs. Aber dank der Compiler-Magie können Tupel-Elemente benannt werden:
var tuple = (name:"Bob", age:23); Console.WriteLine (tuple.name); // Bob Console.WriteLine (tuple.age); // 23
Mit Tupeln können Funktionen mehrere Werte zurückgeben, ohne auf out
Parameter oder zusätzliche Typen zurückgreifen zu müssen:
static (int row, int column) GetFilePosition() => (3, 10); static void Main() { var pos = GetFilePosition(); Console.WriteLine (pos.row); // 3 Console.WriteLine (pos.column); // 10 }
Tupel unterstützen implizit das Dekonstruktionsmuster, sodass du sie leicht in einzelne Variablen zerlegen kannst:
static void Main() { (int row, int column) = GetFilePosition(); // Creates 2 local variables Console.WriteLine (row); // 3 Console.WriteLine (column); // 10 }
Ausdrücke werfen
Vor C# 7 war throw
immer eine Anweisung. Jetzt kann er auch als Ausdruck in Funktionen mit Ausdrucksform erscheinen:
public string Foo() => throw new NotImplementedException();
Ein throw
Ausdruck kann auch in einem ternären bedingten Ausdruck vorkommen:
string Capitalize (string value) => value == null ? throw new ArgumentException ("value") : value == "" ? "" : char.ToUpper (value[0]) + value.Substring (1);
Was ist neu in C# 6.0
C# 6.0, das mit Visual Studio 2015 ausgeliefert wurde, enthält einen Compiler der neuen Generation, der komplett in C# geschrieben wurde. Der neue Compiler, der unter dem Namen "Roslyn" bekannt ist, stellt die gesamte Kompilierungspipeline über Bibliotheken zur Verfügung und ermöglicht es dir, Codeanalysen an beliebigem Quellcode durchzuführen. Der Compiler selbst ist Open Source und der Quellcode ist unter github.com/dotnet/roslyn verfügbar.
Darüber hinaus bietet C# 6.0 einige kleinere, aber wichtige Verbesserungen, die vor allem darauf abzielen, den Code zu entschlacken.
Der Null-Bedingungsoperator ("Elvis") (siehe "Null-Operatoren") verhindert, dass vor dem Aufruf einer Methode oder dem Zugriff auf ein Typmitglied explizit auf Null geprüft werden muss. Im folgenden Beispiel wird result
als Null ausgewertet, anstatt eine NullReferenceException
auszulösen:
System.Text.StringBuilder sb = null; string result = sb?.ToString(); // result is null
Funktionen in Form von Ausdrücken (siehe "Methoden") ermöglichen es, Methoden, Eigenschaften, Operatoren und Indexer, die einen einzigen Ausdruck bilden, im Stil eines Lambda-Ausdrucks zu schreiben:
public int TimesTwo (int x) => x * 2; public string SomeProperty => "Property value";
MitEigenschaftsinitialisierern(Kapitel 3) kannst du einer automatischen Eigenschaft einen Anfangswert zuweisen:
public DateTime TimeCreated { get; set; } = DateTime.Now;
Initialisierte Eigenschaften können auch schreibgeschützt sein:
public DateTime TimeCreated { get; } = DateTime.Now;
Schreibgeschützte Eigenschaften können auch im Konstruktor gesetzt werden, was es einfacher macht, unveränderliche (schreibgeschützte) Typen zu erstellen.
Index-Initialisierer(Kapitel 4) ermöglichen die Initialisierung in einem Schritt für jeden Typ, der einen Indexer bereitstellt:
var dict = new Dictionary<int,string>() { [3] = "three", [10] = "ten" };
DieString-Interpolation (siehe "String-Typ") bietet eine prägnante Alternative zu string.Format
:
string s = $"It is {DateTime.Now.DayOfWeek} today";
MitAusnahmefiltern (siehe "try-Anweisungen und Ausnahmen") kannst du eine Bedingung auf einen Catch-Block anwenden:
string html; try { html = await new HttpClient().GetStringAsync ("http://asef"); } catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout) { ... }
Mit der Direktive using static
(siehe "Namensräume") kannst du alle statischen Mitglieder eines Typs importieren, so dass du diese Mitglieder unqualifiziert verwenden kannst:
using static System.Console; ... WriteLine ("Hello, world"); // WriteLine instead of Console.WriteLine
Der nameof
(Kapitel 3) Operator gibt den Namen einer Variablen, eines Typs oder eines anderen Symbols als String zurück. Dadurch wird vermieden, dass der Code unterbrochen wird, wenn du ein Symbol in Visual Studio umbenennst:
int capacity = 123; string x = nameof (capacity); // x is "capacity" string y = nameof (Uri.Host); // y is "Host"
Und schließlich ist es dir jetzt erlaubt, await
innerhalb von catch
und finally
Blöcken zu verwenden.
Was ist neu in C# 5.0
Die große Neuerung von C# 5.0 war die Unterstützung für asynchrone Funktionen über zwei neue Schlüsselwörter, async
und await
. Asynchrone Funktionen ermöglichen asynchrone Fortsetzungen, die es einfacher machen, reaktionsschnelle und thread-sichere Rich-Client-Anwendungen zu schreiben. Sie erleichtern auch das Schreiben von hochgradig nebenläufigen und effizienten E/A-gebundenen Anwendungen, die nicht für jede Operation eine Thread-Ressource binden. Wir behandeln asynchrone Funktionen ausführlich in Kapitel 14.
Was ist neu in C# 4.0
Mit C# 4.0 wurden vier wichtige Verbesserungen eingeführt:
Dynamisches Binden (Kapitel 4 und 19) verlagert das Binden - also dasAuflösen von Typen und Membern - von der Kompilierzeit auf die Laufzeit und ist in Szenarien nützlich, die sonst komplizierten Reflection-Code erfordern würden. Dynamisches Binden ist auch bei der Interaktion mit dynamischen Sprachen und COM-Komponenten nützlich.
Mit optionalen Parametern(Kapitel 2) können Funktionen Standardparameterwerte angeben, so dass der Aufrufer Argumente weglassen kann, und mit benannten Argumenten kann ein Funktionsaufrufer ein Argument anhand seines Namens und nicht anhand seiner Position identifizieren.
Die Regeln für dieTypvarianz wurden in C# 4.0 (Kapitel 3 und 4) gelockert, so dass Typparameter in generischen Schnittstellen und generischen Delegaten als kovariant oder kontravariant markiert werden können, was natürlichere Typkonvertierungen ermöglicht.
DieCOM-Interoperabilität(Kapitel 24) wurde in C# 4.0 auf drei Arten verbessert. Erstens können Argumente per Referenz ohne das Schlüsselwort ref
übergeben werden (besonders nützlich in Verbindung mit optionalen Parametern). Zweitens können Assemblies, die COM-Interop-Typen enthalten, verlinkt werden, anstatt sie zu referenzieren. Verlinkte Interop-Typen unterstützen die Typäquivalenz, so dass keine primären Interop-Assemblies mehr benötigt werden und die Versionskontrolle und das Deployment kein Problem mehr darstellen. Drittens werden Funktionen, die COM-Variantentypen von verknüpften Interop-Typen zurückgeben, auf dynamic
und nicht auf object
abgebildet, sodass kein Casting mehr erforderlich ist.
Was ist neu in C# 3.0
Die in C# 3.0 hinzugefügten Funktionen konzentrierten sich hauptsächlich auf die sprachintegrierte Abfrage (LINQ). LINQ ermöglicht es, Abfragen direkt in einem C#-Programm zu schreiben und statisch auf Korrektheit zu prüfen und sowohl lokale Sammlungen (wie Listen oder XML-Dokumente) als auch entfernte Datenquellen (wie eine Datenbank) abzufragen. Zu den in C# 3.0 hinzugefügten Funktionen zur Unterstützung von LINQ gehören implizit typisierte lokale Variablen, anonyme Typen, Objektinitialisierungen, Lambda-Ausdrücke, Erweiterungsmethoden, Abfrageausdrücke und Ausdrucksbäume.
Mit implizit typisierten lokalen Variablen (var
Schlüsselwort, Kapitel 2) kannst du den Variablentyp in einer Deklarationsanweisung weglassen, damit der Compiler ihn ableiten kann. Das reduziert das Durcheinander und ermöglicht anonyme Typen(Kapitel 4), also einfache Klassen, die spontan erstellt werden und häufig in der Endausgabe von LINQ-Abfragen verwendet werden. Du kannst auch Arrays implizit typisieren(Kapitel 2).
Objektinitialisierer(Kapitel 3) vereinfachen die Objektkonstruktion, indem sie es dir ermöglichen, Eigenschaften inline nach dem Konstruktoraufruf zu setzen. Objektinitialisierer funktionieren sowohl mit benannten als auch mit anonymen Typen.
Lambda-Ausdrücke(Kapitel 4) sind Miniaturfunktionen, die vom Compiler im laufenden Betrieb erstellt werden; sie sind besonders nützlich bei "fließenden" LINQ-Abfragen(Kapitel 8).
Erweiterungsmethoden (Kapitel 4) erweitern einen bestehenden Typ um neue Methoden (ohne die Definition des Typs zu ändern), sodass sich statische Methoden wie Instanzmethoden anfühlen. Die Abfrageoperatoren von LINQ sind als Erweiterungsmethoden implementiert.
Abfrageausdrücke(Kapitel 8) bieten eine übergeordnete Syntax zum Schreiben von LINQ-Abfragen, die bei der Arbeit mit mehreren Sequenzen oder Bereichsvariablen wesentlich einfacher sein kann.
Expression Trees(Kapitel 8) sind Miniatur-Code Document Object Models (DOMs), die Lambda-Ausdrücke beschreiben, die dem speziellen Typ Expression<TDelegate>
zugeordnet sind. Ausdrucksbäume ermöglichen die Ausführung von LINQ-Abfragen aus der Ferne (z. B. auf einem Datenbankserver), da sie zur Laufzeit introspektiert und übersetzt werden können (z. B. in eine SQL-Anweisung).
C# 3.0 fügte auch automatische Eigenschaften und partielle Methoden hinzu.
Automatische Eigenschaften(Kapitel 3) ersparen die Arbeit beim Schreiben von Eigenschaften, die einfach get
/set
ein privates Hintergrundfeld enthalten, indem sie den Compiler diese Arbeit automatisch erledigen lassen. Partielle Methoden(Kapitel 3) ermöglichen es, dass eine automatisch erzeugte partielle Klasse anpassbare Haken für die manuelle Erstellung bietet, die bei Nichtbenutzung "verschwinden".
Was ist neu in C# 2.0
Die großen Neuerungen in C# 2 waren Generics(Kapitel 3), nullable value types(Kapitel 4), Iteratoren(Kapitel 4) und anonyme Methoden (der Vorgänger der Lambda-Ausdrücke). Diese Funktionen ebneten den Weg für die Einführung von LINQ in C# 3.
C# 2 unterstützte außerdem partielle und statische Klassen sowie eine Reihe kleinerer und verschiedener Funktionen wie den Namespace Alias Qualifier, Friend Assemblies und Puffer mit fester Größe.
Die Einführung von Generika erforderte eine neue CLR (CLR 2.0), da Generika die volle Typentreue zur Laufzeit erhalten.
Get C# 10 in einer Kurzfassung now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.