C# Tutorial
C# Tutorial
C#-Dokumentation
Erste Schritte
Einführung
Typen
Programmbausteine
Wichtige Sprachbereiche
Tutorials
Auswählen der ersten Lektion
Browserbasierte Tutorials
Hello World
Zahlen in C#
Verzweigungen und Schleifen
Listensammlungen
Arbeiten in Ihrer lokalen Umgebung
Erstellen Ihrer Umgebung
Zahlen in C#
Verzweigungen und Schleifen
Listensammlungen
Grundlagen
Programmstruktur
Übersicht
Main-Methode
Top-Level-Anweisungen
Typsystem
Übersicht
Namespaces
Klassen
Datensätze
Schnittstellen
Generics
Anonyme Typen
Objektorientiertes Programmieren
Übersicht
erzwingen
Vererbung
Polymorphismus
Funktionale Techniken
Musterabgleich
Ausschuss
Dekonstruieren von Tupeln und anderen Typen
Ausnahmen und Fehler
Übersicht
Verwenden von Ausnahmen
Ausnahmebehandlung
Erstellen und Auslösen von Ausnahmen
Vom Compiler generierte Ausnahmen
Codierungsstil
Bezeichnernamen
Codekonventionen für C#
Tutorials
Anzeigen von Befehlszeilenargumenten
Einführung in Klassen
Objektorientiertes C#
Vererbung in C# und .NET
Konvertieren von Typen
Erstellen von datengesteuerten Algorithmen mit Musterabgleich
Behandeln einer Ausnahme mit try/catch
Ausführen von Bereinigungscode mit finally
Neues in C#
C# 10.0 (Vorschauversion 7)
C# 9.0
C# 8.0
C# 7.0–7.3
Wichtige Compileränderungen
C#-Versionsgeschichte
Beziehungen zur .NET-Bibliothek
Versionskompatibilität
Tutorials
Erkunden von Datensatztypen
Lesen von Top-Level-Anweisungen
Erkunden von Mustern in Objekten
Sicheres Aktualisieren von Schnittstellen mit Standardschnittstellenmethoden
Erstellen einer Mixin-Funktionalität mit Standardschnittstellenmethoden
Erkunden von Indizes und Bereichen
Arbeiten mit Verweistypen, die Nullwerte zulassen
Generieren und Nutzen asynchroner Streams
Tutorials
Erkunden von Zeichenfolgeninterpolation – interakt
Erkunden von Zeichenfolgeninterpolation – in Ihrer Umgebung
Erweiterte Szenarios für Zeichenfolgeninterpolation
Konsolenanwendung
REST-Client
Arbeiten mit LINQ
Verwenden von Attributen
C#-Konzepte
Nullwerte zulassende Verweistypen
Nullverweismigrationen
Auflösen von Nullable-Warnungen
Methoden
Eigenschaften
Indexer
Iterators
Delegaten und Ereignisse
Einführung in Delegaten
System.Delegate und das delegate-Schlüsselwort
Stark typisierte Delegate
Gängige Muster für Delegate
Einführung in Ereignisse
Standardereignismuster in .NET
Das aktualisierte .NET-Ereignismuster
Unterscheidung zwischen Delegaten und Ereignissen
Language-Integrated Query (LINQ)
Übersicht über LINQ
Grundlagen zu Abfrageausdrücken
LINQ in C#
Schreiben von LINQ-Abfragen in C#
Abfrage einer Auflistung von Objekten
Zurückgeben einer Abfrage aus einer Methode
Speichern der Ergebnisse einer Abfrage im Speicher
Gruppieren von Abfrageergebnissen
Erstellen einer geschachtelten Gruppe
Ausführen einer Unterabfrage für eine Gruppierungsoperation
Gruppieren von Ergebnissen nach zusammenhängenden Schlüsseln
Dynamisches Festlegen von Prädikatfiltern zur Laufzeit
Ausführen von inneren Verknüpfungen
Ausführen von Gruppenverknüpfungen
Ausführen von Left Outer Joins
Sortieren der Ergebnisse einer Join-Klausel
Verknüpfen mithilfe eines zusammengesetzten Schlüssels
Ausführen von benutzerdefinierten Verknüpfungsoperationen
Behandeln von NULL-Werten in Abfrageausdrücken
Behandeln von Ausnahmen in Abfrageausdrücken
Schreiben von sicherem und effizientem Code
Ausdrucksbaumstrukturen
Einführung in Ausdrucksbaumstrukturen
Ausdrucksbaumstrukturen mit Erläuterung
Framework-Typen, die Ausdrucksbaumstrukturen unterstützen
Ausführen von Ausdrücken
Interpretieren von Ausdrücken
Erstellen von Ausdrücken
Übersetzen von Ausdrücken
Zusammenfassung
Native Interoperabilität
Versionskontrolle
Artikel zu Vorgehensweisen in C#
Artikelindex
Aufteilen von Zeichenfolgen in Teilzeichenfolgen
Verketten von Zeichenfolgen
Suchzeichenfolgen
Ändern von Zeichenfolgeninhalten
Vergleichen von Zeichenfolgen
Abfangen einer Nicht-CLS-Ausnahme
Das .NET Compiler Platform SDK (Roslyn APIs)
Übersicht: das .NET Compiler Platform SDK (Roslyn APIs)
Grundlegendes zum API-Modell von Compilern
Arbeiten mit der Syntax
Arbeiten mit der Semantik
Arbeiten mit einem Arbeitsbereich
Untersuchen von Code mit der Syntaxschnellansicht
Quellgeneratoren
Schnellstarts
Syntaxanalyse
Semantikanalyse
Syntaxtransformation
Tutorials
Erstellen Ihres ersten Analysetools und Codefixes
C#-Programmierhandbuch
Übersicht
Übersicht
Programmierkonzepte
Übersicht
Asynchrone Programmierung
Übersicht
Asynchrone Programmierszenarios
Aufgabenbasiertes asynchrones Programmiermodell
Asynchrone Rückgabetypen
Abbrechen von Tasks
Abbrechen einer Aufgabenliste
Abbrechen asynchroner Tasks nach einem bestimmten Zeitraum
Verarbeiten asynchroner Aufgaben nach Abschluss
Asynchroner Dateizugriff
Attribute
Übersicht
Erstellen benutzerdefinierter Attribute
Zugriff auf Attribute mit Reflektion
Erstellen einer Union in C/C++ mit Attributen
Auflistungen
Kovarianz und Kontravarianz
Übersicht
Abweichungen bei generischen Schnittstellen
Erstellen von Varianten generischer Schnittstellen
Verwenden von Varianz in Schnittstellen für generische Sammlungen
Varianz bei Delegaten
Varianz bei Delegaten
Verwenden von Varianz für die generischen Delegaten Func und Action
Ausdrucksbaumstrukturen
Übersicht
Ausführen von Ausdrucksbaumstrukturen
Ändern von Ausdrucksbaumstrukturen
Verwenden von Ausdrucksbaumstrukturen zum Erstellen dynamischer Abfragen
Debuggen von Ausdrucksbäumen in Visual Studio
DebugView-Syntax
Iterators
Language-Integrated Query (LINQ)
Übersicht
Erste Schritte mit LINQ in C#
Einführung in LINQ-Abfragen
LINQ und generische Typen
Grundlegende LINQ-Abfragevorgänge
Datentransformationen mit LINQ
Typbeziehungen in LINQ-Abfragevorgängen
Abfragesyntax und Methodensyntax in LINQ
C#-Funktionen mit LINQ-Unterstützung
Exemplarische Vorgehensweise: Schreiben von Abfragen in C# (LINQ)
Übersicht über Standardabfrageoperatoren
Übersicht
Abfrageausdruckssyntax für Standardabfrageoperatoren
Klassifizierung von Standardabfrageoperatoren nach Ausführungsart
Sortieren von Daten
Mengenvorgänge
Filtern von Daten
Quantifizierervorgänge
Projektionsvorgänge
Partitionieren von Daten
Verknüpfungsvorgänge
Gruppieren von Daten
Generierungsvorgänge
Gleichheitsvorgänge
Elementvorgänge
Konvertieren von Datentypen
Verkettungsvorgänge
Aggregationsvorgänge
LINQ to Objects
Übersicht
LINQ und Zeichenfolgen
Artikeln zu Vorgehensweisen
Zählen der Vorkommen eines Worts in einer Zeichenfolge (LINQ)
Abfragen von Sätzen, die eine angegebene Gruppe von Wörtern (LINQ)
enthalten
Abfragen von Zeichen in einer Zeichenfolge (LINQ)
Kombinieren von LINQ-Abfragen mit regulären Ausdrücken
Suchen der festgelegten Differenz zwischen zwei Listen (LINQ)
Sortieren oder Filtern von Textdaten nach einem beliebigen Wort oder Feld
(LINQ)
Neuordnen der Felder einer Datei mit Trennzeichen (LINQ)
Verbinden und Vergleichen von Zeichenfolgenauflistungen (LINQ)
Füllen von Objektsammlungen aus mehreren Quellen (LINQ)
Teilen einer Datei in mehrere Dateien durch das Verwenden von Gruppen
(LINQ)
Verknüpfen des Inhalts unterschiedlicher Dateien (LINQ)
Berechnen von Spaltenwerten in einer CSV-Textdatei (LINQ)
LINQ und Reflection
Abfragen der Metadaten einer Assembly mit Reflektion (LINQ)
LINQ und Dateiverzeichnisse
Übersicht
Abfragen von Dateien mit einem angegebenen Attribut oder Namen
Gruppieren von Dateien nach Erweiterung (LINQ)
Abfragen der Gesamtzahl an Bytes in mehreren Ordnern (LINQ)
Vergleichen des Inhalts von zwei Ordnern (LINQ)
Abfragen der größten Datei oder der größten Dateien in einer
Verzeichnisstruktur (LINQ)
Abfragen von Dateiduplikaten in einer Verzeichnisstruktur (LINQ)
Abfragen des Inhalts von Dateien in einem Ordner (LINQ)
Abfragen von ArrayList mit LINQ
Hinzufügen von benutzerdefinierten Methoden zu LINQ-Abfragen
LINQ to ADO.NET (Portalseite)
Aktivieren einer Datenquelle für LINQ-Abfragen
Visual Studio-IDE und Toolunterstützung für LINQ
Spiegelung
Serialisierung (C#)
Übersicht
Schreiben von Objektdaten in eine XML-Datei
Lesen von Objektdaten aus einer XML-Datei
Exemplarische Vorgehensweise: Beibehalten eines Objekts in Visual Studio
Anweisungen, Ausdrücke und Operatoren
Übersicht
Anweisungen
Ausdruckskörpermember
Gleichheit und Gleichheitsvergleiche
Übereinstimmungsvergleiche
Definieren von Wertgleichheit für einen Typ
Überprüfen auf Verweisgleichheit (Identität)
Typen
Umwandlung und Typkonvertierungen
Boxing und Unboxing
Konvertieren eines Bytearrays in einen ganzzahligen Typ
Konvertieren einer Zeichenfolge in eine Zahl
Konvertieren zwischen Hexadezimalzeichenfolgen und numerischen Typen
Verwenden von dynamischen Typen
Exemplarische Vorgehensweise: Erstellen und Verwenden von dynamischen
Objekten (C# und Visual Basic)
Klassen, Strukturen und Datensätze
Polymorphie
Versionsverwaltung mit den Schlüsselwörtern "override" und "new"
Wann müssen die Schlüsselwörter "override" und "new" verwendet werden?
Überschreiben der ToString-Methode
Member
Member (Übersicht)
Abstrakte und versiegelte Klassen und Klassenmember
Statische Klassen und statische Klassenmember
Zugriffsmodifizierer
Felder
Konstanten
Definieren von abstrakten Eigenschaften
Definieren von Konstanten in C#
Eigenschaften
Übersicht über Eigenschaften
Verwenden von Eigenschaften
Schnittstelleneigenschaften
Einschränken des Zugriffsmethodenzugriffs
Deklarieren und Verwenden von Lese-/Schreibeigenschaften
Automatisch implementierte Eigenschaften
Implementieren einer einfachen Klasse mit automatisch implementierten
Eigenschaften
Methoden
Methoden (Übersicht)
Lokale Funktionen
Ref-Rückgaben und lokale ref-Variablen
Parameter
Übergeben von Parametern
Übergeben von Werttypparametern
Übergeben von Verweistypparametern
Unterschiede zwischen dem Übergeben einer Struktur und dem Übergeben
eines Klassenverweises an eine Methode
Implizit typisierte lokale Variablen
Verwenden von implizit typisierten lokalen Variablen und Arrays in einem
Abfrageausdruck
Erweiterungsmethoden
Implementieren und Aufrufen einer benutzerdefinierten Erweiterungsmethode
Erstellen einer neuen Methode für eine Enumeration
Benannte und optionale Argumente
Verwenden von benannten und optionalen Argumenten in der Office-
Programmierung
Konstruktoren
Konstruktoren (Übersicht)
Verwenden von Konstruktoren
Instanzenkonstruktoren
Private Konstruktoren
Statische Konstruktoren
Schreiben eines Kopierkonstruktors
Finalizer
Objekt- und Auflistungsinitialisierer
Initialisieren von Objekten mithilfe eines Objektinitialisierers
Initialisieren eines Wörterbuchs mit einem Sammlungsinitialisierer
Geschachtelte Typen
Partielle Klassen und Methoden
Zurückgeben von Teilmengen von Elementeigenschaften in einer Abfrage
Schnittstellen
Explizite Schnittstellenimplementierung
Explizites Implementieren von Schnittstellenmembern
Explizites Implementieren von Membern zweier Schnittstellen
Delegaten
Übersicht
Verwenden von Delegaten
Delegaten mit benannten im Vergleich zu Anonyme Methoden
Kombinieren von Delegaten (Multicastdelegaten) (C#-Programmierhandbuch)
Deklarieren, Instanziieren und Verwenden von Delegaten
Arrays
Übersicht
Eindimensionale Arrays
Mehrdimensionale Arrays
Verzweigte Arrays
Verwenden von „foreach“ mit Arrays
Übergeben von Arrays als Argumente
Implizit typisierte Arrays
Zeichenfolgen
Programmieren mit Zeichenfolgen
Bestimmen, ob eine Zeichenfolge einen numerischen Wert darstellt
Indexer
Übersicht
Verwenden von Indexern
Indexer in Schnittstellen
Vergleich zwischen Eigenschaften und Indexern
Ereignisse
Übersicht
Abonnieren von Ereignissen und Kündigen von Ereignisabonnements
Veröffentlichen von Ereignissen, die den .NET-Richtlinien entsprechen
Auslösen von Basisklassenereignissen in abgeleiteten Klassen
Implementieren von Schnittstellenereignissen
Implementieren benutzerdefinierter Ereignisaccessoren
Generics
Generische Typparameter
Einschränkungen für Typparameter
Generische Klassen
Generische Schnittstellen
Generische Methoden
Generics und Arrays
Generische Delegate
Unterschiede zwischen C++-Vorlagen und C#-Generics
Generics zur Laufzeit
Generics und Reflexion
Generische Typen und Attribute
Das Dateisystem und die Registrierung
Übersicht
Durchlaufen einer Verzeichnisstruktur
Abrufen von Informationen über Dateien, Ordner und Laufwerke
Erstellen einer Datei oder eines Ordners
Kopieren, Löschen und Verschieben von Dateien und Ordnern
Bereitstellen eines Statusdialogfelds für Dateioperationen
Schreiben in eine Textdatei
Lesen aus einer Textdatei
Zeilenweises Lesen einer Textdatei
Erstellen eines Schlüssels in der Registrierung
Interoperabilität
.NET-Interoperabilität
Überblick über die Interoperabilität
Zugreifen auf Office-Interop-Objekte mithilfe von Visual C#-Funktionen
Indizierte Eigenschaften bei der COM-Interop-Programmierung
Verwenden eines Plattformaufrufs zum Wiedergeben einer Wavedatei
Exemplarische Vorgehensweise: Office-Programmierung (C# und Visual Basic)
COM-Beispielklasse
Sprachreferenz
Übersicht
Angeben der Sprachversion
Typen
Werttypen
Übersicht
Integrale numerische Typen
Native ganzzahlige nint- und nuint-Typen
Numerische Gleitkommatypen
Integrierte numerischer Konvertierungen
bool
char
Enumerationstypen
Strukturtypen
Tupeltypen
Auf NULL festlegbare Werttypen
Verweistypen
Funktionen von Verweistypen
Integrierte Referenztypen
Datensatz (record)
class
interface
Nullwerte zulassende Verweistypen
void
var
Integrierte Typen
Nicht verwaltete Typen
Standardwerte
Keywords
Übersicht
Modifizierer
Zugriffsmodifizierer
Kurzreferenz
Zugriffsebenen
Zugriffsdomäne
Einschränkungen bei der Verwendung von Zugriffsebenen
internal
private
protected
public
protected internal
private protected
abstract
async
const
event
extern
in (generischer Modifizierer)
new (Membermodifizierer)
out (generischer Modifizierer)
override
readonly
sealed
static
unsafe
virtual
volatile
Anweisungsschlüsselwörter
Anweisungskategorien
Sprunganweisungen
break
continue
goto
return
Ausnahmebehandlungsanweisungen
throw
try-catch
try-finally
try-catch-finally
Checked und Unchecked
Übersicht
checked
unchecked
fixed-Anweisung
lock-Anweisung
Methodenparameter
Übergeben von Parametern
params
Modifizierer für in-Parameter
ref
Modifizierer für out-Parameter
Namespaceschlüsselwörter
namespace
using
Kontexte für using
using-Anweisung
using-Anweisung
extern-Alias
Generische Schlüsselwörter für die Typeinschränkung
new-Einschränkung
where
Zugriffsschlüsselwörter
base
this
Literalschlüsselwörter
NULL
„true” und „false”.
default
Kontextabhängige Schlüsselwörter
Kurzreferenz
add
get
init
partial (Typ)
partial (Methode)
remove
set
when (Filterbedingung)
value
yield
Abfrageschlüsselwörter
Kurzreferenz
from-Klausel
where-Klausel
select-Klausel
group-Klausel
into
orderby-Klausel
join-Klausel
let-Klausel
ascending
descending
on
equals
by
in
Operatoren und Ausdrücke
Übersicht
Arithmetische operatoren
Logische boolesche Operatoren
Bitweise und Schiebeoperatoren
Gleichheitsoperatoren
Vergleichsoperatoren
Operatoren und Ausdrücke für den Memberzugriff
Cast-Ausdrücke und Operatoren für Typtests
Benutzerdefinierte Konvertierungsoperatoren
Auf Zeiger bezogene Operatoren
Zuweisungsoperatoren
Lambdaausdrücke
Muster
+ +=-Operatoren
- -=-Operatoren
?:-Operator
! NULL-toleranter Operator
?? und ??=-Operatoren
=>-Operator
::-Operator
Await-Operator
Ausdrücke mit Standardwert
delegate-Operator
is-Operator
nameof-Ausdruck
new-Operator
sizeof-Operator
stackalloc-Ausdruck
switch-Ausdruck
true- und false-Operatoren
with-Ausdruck
Überladen von Operatoren
Anweisungen
Iterationsanweisungen
Auswahlanweisungen
Sonderzeichen
Übersicht
$ -- Zeichenfolgeninterpolation
@ -- ausführlicher Bezeichner
Vom Compiler gelesene Attribute
Globale Attribute
Aufruferinformationen
Statische Analysen, die NULL-Werte zulassen
Sonstiges
Unsicherer Code und Zeiger
Präprozessordirektiven
Compileroptionen
Übersicht
Sprachoptionen
Ausgabeoptionen
Eingabeoptionen
Fehler und Warnungsoptionen
Optionen für die Codegenerierung
Sicherheitsoptionen
Ressourcenoptionen
Sonstige Optionen
Erweiterte Optionen
XML-Dokumentationskommentare
Dokumentation zum Generieren von APIs
Recommended tags (Empfohlene Tags)
Beispiele
Compilermeldungen
Spezifikationen
C# 6.0 draft specification (C# 6.0 - Entwurfsspezifikationen)
Einführung
Lexikalische Struktur
Grundlegende Konzepte
Typen
Variablen
Konvertierungen
Ausdrücke
Anweisungen
Namespaces
Klassen
Strukturen
Arrays
Schnittstellen
Enumerationen
Delegaten
Ausnahmen
Attribute
Unsicherer Code
Dokumentationskommentare
C# 7.0- bis 10.0-Features
C# 7.0-Features
Musterabgleich
Lokale Funktionen
out-Variablendeklaration
Throw-Ausdrücke
Binäre Literale
Zahlentrennzeichen
Asynchrone Tasktypen
C# 7.1-Features
Async Main-Methode
Standardausdrücke
Ableiten von Tupelnamen
Musterabgleich mit Generics
C# 7.2-Features
Schreibgeschützte Verweise
Sicherheit zur Kompilierzeit für ref-ähnliche Typen
Nicht schließende benannte Argumente
Privat geschützt
Bedingter ref-Ausdruck
Trennzeichen für vorangestellte Ziffern
C# 7.3-Features
Nicht verwaltete generische Typeneinschränkungen
Für `fixed`-Felder zur Indizierung ist kein Anheften erforderlich unabhängig vom
Kontext „verschiebbar/nicht verschiebbar“
Musterbasierte `fixed`-Anweisung
Lokale ref-Neuzuweisung
Stackalloc-Arrayinitialisierer
Auf Felder ausgerichtete Attribute für automatisch implementierte Eigenschaft
Ausdrucksvariablen in Initialisierern
Tupelgleichheit (==) und -ungleichheit (! =)
Verbesserte Überladungskandidaten
C# 8.0-Features
Nullable-Verweistypen – Vorschlag
Rekursiver Musterabgleich
Standardschnittstellenmethoden
Asynchrone Datenströme
Bereiche
Musterbasierte Verwendung und using-Deklarationen
Statische lokale Funktionen
NULL-Sammelzuweisung
Schreibgeschützte Instanzmember
stackalloc bei Verschachtelungen
C# 9.0-Features
Datensätze
Top-Level-Anweisungen
Nullable-Verweistypen – Spezifikation
Verbesserungen am Musterabgleich:
init-only-Setter
Zieltypisierte neue Ausdrücke
Modulinitialisierer
Erweitern von partiellen Methoden
Statische anonyme Funktionen
Bedingter Ausdruck mit Zieltyp
Kovariante Rückgabetypen
Erweiterung GetEnumerator in foreach-Schleifen
Parameter zum Verwerfen von Lambdafunktion
Attribute in lokalen Funktionen
Integerwerte mit nativer Größe
Funktionszeiger
Unterdrücken der Ausgabe des Flags „localsinit“
Nicht eingeschränkte Typparameteranmerkungen
C# 10.0-Features
Datensatzstrukturen
Parameterlose Strukturkonstruktoren
Globale using-Anweisung
Dateibereichsnamespaces
Muster für erweiterte Eigenschaften
Verbesserte interpolierte Zeichenfolgen
Konstante interpolierte Zeichenfolgen
Lambdaverbesserungen
Aufruferargumentausdruck
Erweiterte #line-Direktiven
Generische Attribute
Verbesserte Analyse der definitiven Zuweisung
AsyncMethodBuilder-Überschreibung
Überblick über C#
04.11.2021 • 13 minutes to read
.NET-Architektur
C#-Programme werden auf Grundlage von .NET ausgeführt, ein virtuelles Ausführungssystem namens
Common Language Runtime (CLR) sowie Klassenbibliotheken. Die CLR ist die Implementierung der Common
Language Infrastructure (CLI) von Microsoft, ein internationaler Standard. Die CLI ist die Grundlage für das
Erstellen von Ausführungs- und Entwicklungsumgebungen, in denen Sprachen und Bibliotheken nahtlos
zusammenarbeiten.
Der in C# geschriebene Quellcode wird in eine Zwischensprache kompiliert, die konform mit der CLI-
Spezifikation ist. Der IL-Code wird zusammen mit Ressourcen wie z. B. Bitmaps und Zeichenfolgen in einer
Assembly gespeichert, die normalerweise die Erweiterung .dll aufweist. Eine Assembly enthält ein Manifest, das
Informationen über die Typen, die Version und die Kultur der Assembly bereitstellt.
Wenn das C#-Programm ausgeführt wird, wird die Assembly in die CLR geladen. Die CLR konvertiert den IL-
Code mithilfe der JIT-Kompilierung (Just-In-Time) in native Computeranweisungen. Die CLR stellt weitere
Dienste zur automatischen Garbage Collection, Ausnahmebehandlung und Ressourcenverwaltung bereit. Code,
der von der CLR ausgeführt wird, wird manchmal als „verwalteter Code“ bezeichnet. „Nicht verwalteter Code“
wird in native Computersprache kompiliert, die auf eine bestimmte Plattform zielt.
Eines der wichtigsten Features in .NET ist die Sprachinteroperabilität. Der vom C#-Compiler erzeugte IL-Code
entspricht dem allgemeinen Typsystem (CTS, Common Type Specification). Der über C# generierte IL-Code kann
mit Code interagieren, der über die .NET-Versionen von F#, Visual Basic oder C++ generiert wurde. Es gibt mehr
als 20 weitere CTS-kompatible Sprachen. Eine einzelne Assembly kann mehrere Module enthalten, die in
verschiedenen .NET-Sprachen geschrieben wurden. Die Typen können aufeinander verweisen, als wären sie in
der gleichen Sprache geschrieben.
Zusätzlich zu den Laufzeitdiensten enthält .NET auch umfangreiche Bibliotheken. Diese Bibliotheken
unterstützen viele verschieden Workloads. Sie sind in Namespaces organisiert, die eine große Bandbreite
nützlicher Funktionen bereitstellen. Die Bibliotheken beinhalten alles von der Dateiein- und -ausgabe über die
Bearbeitung von Zeichenfolgen, XML-Analyse und Webanwendungs-Frameworks bis hin zu Windows Forms-
Steuerelementen. Eine typische C#-Anwendung verwendet für die Ausführung von Routinevorgängen ausgiebig
die .NET-Klassenbibliothek.
Weitere Informationen zu .NET finden Sie in der Übersicht über .NET.
Hello World
Das Programm „Hello, World“ wird für gewöhnlich zur Einführung einer Programmiersprache verwendet. Hier
ist es in C#:
using System;
class Hello
{
static void Main()
{
Console.WriteLine("Hello, World");
}
}
Das Programm „Hello, World“ wird mit einer using -Richtlinie gestartet, die auf den System -Namespace
verweist. Namespaces bieten eine hierarchische Möglichkeit zum Organisieren von C#-Programmen und -
Bibliotheken. Namespaces enthalten Typen und andere Namespaces. Beispiel: Der System -Namespace enthält
eine Reihe von Typen, wie etwa die Console -Klasse, auf die im Programm verwiesen wird, und eine Reihe
anderer Namespaces, wie etwa IO und Collections . Eine using -Richtlinie, die auf einen bestimmten
Namespace verweist, ermöglicht die nicht qualifizierte Nutzung der Typen, die Member dieses Namespace sind.
Aufgrund der using -Direktive kann das Programm Console.WriteLine als Abkürzung für
System.Console.WriteLine verwenden.
Die Hello-Klasse, die vom Programm „Hello, World“ deklariert wird, verfügt über einen einzelnen Member: die
Main -Methode. Die Main -Methode wird mit dem Modifizierer static deklariert. Auch wenn Instanzmethoden
mit dem Schlüsselwort this auf eine bestimmte einschließende Objektinstanz verweisen können, agieren
statische Methoden ohne Verweis auf ein bestimmtes Objekt. Gemäß Konvention fungiert eine statische
Methode mit der Bezeichnung Main als Einstiegspunkt eines C#-Programms.
Die Ausgabe des Programms wird anhand der WriteLine -Methode der Console -Klasse im System -Namespace
generiert. Diese Klasse wird anhand der Standardklassenbibliotheken bereitgestellt, auf die standardmäßig
automatisch vom Compiler verwiesen wird.
Typen und Variablen
Ein Typ definiert die Struktur und das Verhalten von allen Daten in C#. Die Deklaration eines Typs kann seine
Member, seinen Basistyp, die Schnittstellen, die er implementiert, und die für diesen Typ zulässigen Operationen
enthalten. Eine Variable ist eine Bezeichnung, die auf einen bestimmten Instanzentyp verweist.
Es gibt zwei Arten von Typen in C#: Werttypen und Verweistypen. Variablen von Werttypen enthalten ihre
tatsächlichen Daten. Variablen von Verweistypen speichern hingegen Verweise auf ihre Daten – letztere werden
als Objekte bezeichnet. Dank Verweistypen können zwei Variablen auf das gleiche Objekt verweisen. So können
auf eine Variable angewendete Vorgänge das Objekt beeinflussen, auf das die andere Variable verweist. Bei
Werttypen besitzen die Variablen jeweils eigene Kopien der Daten. Auf eine Variable angewendete Vorgänge
können sich nicht auf die andere Variable auswirken (außer im Fall der Parametervariablen ref und out ).
Ein Bezeichner ist ein Variablenname. Ein Bezeichner ist eine Sequenz von Unicode-Zeichen ohne Leerzeichen.
Ein Bezeichner kann ein in C# reserviertes Wort sein, wenn ihm das Präfix @ vorangestellt ist. Die Verwendung
eines reservierten Worts als Bezeichner kann nützlich sein, wenn Sie mit anderen Sprachen interagieren.
C#-Werttypen sind weiter unterteilt in einfache Typen, Enumerationstypen, Strukturtypen, Nullable-Werttypen
und Tuple-Werttypen. C#-Verweistypen sind weiter unterteilt in Klassentypen, Schnittstellentypen, Arraytypen
und Delegattypen.
Im Folgenden finden Sie eine Übersicht des C#-Typsystems.
Werttypen
Einfache Typen
Integral mit Vorzeichen: sbyte , short , int , long
Integral ohne Vorzeichen: byte , ushort , uint , ulong
Unicode-Zeichen: char , das ein Zeichen als UTF-16-Codeeinheit darstellt
Binäres Gleitkomma (IEEE): float , double
Dezimale Gleitkommazahl mit hoher Genauigkeit: decimal
Boolesch: bool dient zur Darstellung boolescher Werte, die entweder true oder false sind
Enumerationstypen
Benutzerdefinierte Typen der Form enum E {...} . Ein enum -Typ ist ein eigenständiger Typ mit
einer benannten Konstante. Jeder enum -Typ verfügt über einen zugrunde liegenden Typ, der
einer der acht ganzzahligen Typen sein muss. Der Satz von Werten eines enum -Typs ist mit
dem Satz von Werten des zugrunde liegenden Typs identisch.
Strukturtypen
Benutzerdefinierte Typen der Form struct S {...}
Auf NULL festlegbare Werttypen
Erweiterungen aller anderen Werttypen mit einem null -Wert
Tupelwerttypen
Benutzerdefinierte Typen der Form (T1, T2, ...)
Verweistypen
Klassentypen
Ultimative Basisklasse aller anderen Typen: object
Unicode-Zeichenfolgen: string , die eine Sequenz von UTF-16-Codeeinheiten darstellt
Benutzerdefinierte Typen der Form class C {...}
Schnittstellentypen
Benutzerdefinierte Typen der Form interface I {...}
Array types (Arraytypen)
Eindimensionale Arrays, mehrdimensionale Arrays und Jagged Arrays, beispielsweise int[] ,
int[,] und int[][]
Delegattypen
Benutzerdefinierte Typen der Form delegate int D(...)
C#-Programme verwenden Typdeklarationen, um neue Typen zu erstellen. Eine Typdeklaration gibt den Namen
und die Member des neuen Typs an. Sechs Typkategorien von C# können von Benutzern definiert werden:
Klassentypen, Strukturtypen, Schnittstellentypen, Enumerationstypen, Delegattypen und Tuple-Werttypen. Sie
können außerdem record -Typen deklarieren, entweder record struct oder record class . Datensatztypen
verfügen über vom Compiler synthetisierte Member. Sie verwenden Datensätze in erster Linie zum Speichern
von Werten mit minimalem zugeordneten Verhalten.
Ein class -Typ definiert eine Datenstruktur, die Datenmember (Felder) und Funktionsmember (Methoden,
Eigenschaften usw.) enthält. Klassentypen unterstützen einzelne Vererbung und Polymorphie. Dies sind
Mechanismen, durch die abgeleitete Klassen erweitert und Basisklassen spezialisiert werden können.
Ein struct -Typ ähnelt einem Klassentyp, da er eine Struktur mit Datenmembern und Funktionsmembern
darstellt. Im Gegensatz zu Klassen sind Strukturen Werttypen, die in der Regel keine Heapzuordnung
erfordern. Strukturtypen unterstützen keine benutzerdefinierte Vererbung, und alle Strukturtypen erben
implizit vom Typ object .
Ein interface -Typ definiert einen Vertrag als benannte Gruppe öffentlicher Member. Ein class - oder
struct -Typ, der einen interface -Typ implementiert, muss Implementierungen der Member der
Schnittstelle bereitstellen. Eine interface kann von mehreren Basisschnittstellen erben, und eine class
oder struct kann mehrere Schnittstellen implementieren.
Ein delegate -Typ stellt Verweise auf Methoden mit einer bestimmten Parameterliste und dem Rückgabetyp
dar. Delegate ermöglichen die Behandlung von Methoden als Entitäten, die Variablen zugewiesen und als
Parameter übergeben werden können. Delegate werden analog zu Funktionstypen von funktionalen
Sprachen bereitgestellt. Außerdem ähneln sie konzeptionell Funktionszeigern, die es in einigen anderen
Sprachen gibt. Im Gegensatz zu Funktionszeigern sind Delegaten objektorientiert und typsicher.
Die Typen, class , struct , interface und delegate unterstützen Generics, wodurch sie mit anderen Typen
parametrisiert werden können.
C# unterstützt ein- und mehrdimensionale Arrays aller beliebigen Typen. Im Gegensatz zu den oben
aufgeführten Typen müssen Arraytypen nicht deklariert werden, bevor sie verwendet werden können.
Stattdessen werden Arraytypen erstellt, indem hinter einen Typnamen eckige Klammern gesetzt werden. int[]
ist beispielsweise ein eindimensionales Array aus int , int[,] ein zweidimensionales Array aus int , während
int[][] ein eindimensionales Array aus eindimensionalen Arrays oder ein Jagged Array aus int ist.
Nullable-Typen erfordern keine getrennte Definition. Für jeden Non-Nullable-Typ T gibt es einen
entsprechenden Nullable-Typ T? , der einen zusätzlichen Wert, null , enthalten kann. Beispielsweise ist int?
ein Typ, der jeden 32-Bit-Ganzzahlwert oder den Wert null enthalten kann. string? ist ein Typ, der beliebige
string -Typen oder den Wert null enthalten kann.
Das C#-Typsystem ist dahingehend vereinheitlicht, dass ein Wert eines beliebigen Typs als object behandelt
werden kann. Jeder Typ in C# ist direkt oder indirekt vom object -Klassentyp abgeleitet, und object ist die
ultimative Basisklasse aller Typen. Werte von Verweistypen werden als Objekte behandelt, indem die Werte
einfach als Typ object angezeigt werden. Werte von Werttypen werden durch Ausführen von Boxing- und
Unboxingvorgängen als Objekte behandelt. Im folgenden Beispiel wird ein int -Wert in ein object und wieder
in einen int -Wert konvertiert.
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing
Wenn ein Wert eines Werttyps einem object -Verweis zugewiesen wird, wird eine „Box“ zugeordnet, die den
Wert enthalten soll. Bei dieser Box handelt es sich um eine Instanz eines Verweistyps, und der Wert wird in diese
Box kopiert. Wenn umgekehrt ein object -Verweis in einen Werttyp umgewandelt wird, wird überprüft, ob der
object -Typ, auf den verwiesen wird, eine Box des korrekten Werttyps ist. Nach erfolgreicher Überprüfung wird
der Wert in der Box zum Werttyp kopiert.
Aus dem einheitlichen C#-Typensystem resultiert, dass Werttypen „bei Nachfrage“ als object -Verweise
behandelt werden. Aufgrund der Vereinheitlichung können Bibliotheken für allgemeine Zwecke, die den Typ
object verwenden, mit allen Typen verwendet werden können, die von object abgeleitet werden, wozu
sowohl Verweis- als auch Werttypen zählen.
Es gibt mehrere Arten von Variablen in C#, einschließlich Feldern, Arrayelementen, lokalen Variablen und
Parametern. Variablen stellen Speicherorte dar. Jede Variable hat, wie nachstehend gezeigt, einen Typ, der
bestimmt, welche Werte in der Variablen gespeichert werden können.
Nicht auf NULL festlegbarer Werttyp
Ein Wert genau dieses Typs
Auf NULL festlegbarer Werttyp
Ein null -Wert oder ein Wert genau dieses Typs
object
Ein null -Verweis, ein Verweis auf ein Objekt eines beliebigen Verweistyps oder ein Verweis auf einen
geschachtelten Wert eines beliebigen Werttyps
Klassentyp
Ein null -Verweis, ein Verweis auf eine Instanz dieses Klassentyps oder ein Verweis auf eine Instanz
einer Klasse, die von diesem Klassentyp abgeleitet ist
Schnittstellentyp
Ein null -Verweis, ein Verweis auf eine Instanz eines Klassentyps, der diesen Schnittstellentyp
implementiert, oder ein Verweis auf einen geschachtelten Wert eines Werttyps, der diesen
Schnittstellentyp implementiert
Arraytyp
Ein null -Verweis, ein Verweis auf eine Instanz dieses Arraytyps oder ein Verweis auf eine Instanz
eines kompatiblen Arraytyps
Delegattyp
Ein null -Verweis oder ein Verweis auf eine Instanz eines kompatiblen Delegattyp
Programmstruktur
Die wichtigsten Organisationskonzepte in C# sind *Programme _, Namespace, Typen, Member und Assemblys
Programme deklarieren Typen, die Member enthalten, und können in Namespaces organisiert werden. Klassen,
Strukturen und Schnittstellen sind Beispiele von Typen. Felder, Methoden, Eigenschaften und Ereignisse sind
Beispiele für Member. Wenn C#-Programme kompiliert werden, werden sie physisch in Assemblys gepackt.
Assemblys haben in der Regel die Erweiterung .exe oder .dll , je nachdem, ob sie Anwendungen oder
_*Bibliotheken** implementieren.
Nehmen Sie als einfaches Beispiel eine Assembly, die den folgenden Code enthält:
using System;
namespace Acme.Collections
{
public class Stack<T>
{
Entry _top;
public T Pop()
{
if (_top == null)
{
throw new InvalidOperationException();
}
T result = _top.Data;
_top = _top.Next;
return result;
}
class Entry
{
public Entry Next { get; set; }
public T Data { get; set; }
Der vollqualifizierte Name dieser Klasse ist Acme.Collections.Stack . Die Klasse enthält mehrere Member: ein
Feld mit dem Namen top , zwei Methoden mit dem Namen Push und Pop sowie eine geschachtelte Klasse mit
dem Namen Entry . Die Entry -Klasse enthält weitere drei Member: ein Feld mit dem Namen next , ein Feld
mit dem Namen data und einen Konstruktor. Stack ist eine generische Klasse. Sie hat einen Typparameter, T ,
der bei seiner Verwendung durch einen konkreten Typ ersetzt wird.
Ein Stapel ist eine FILO-Sammlung (First In, Last Out). Neue Elemente werden am Anfang des Stapels
hinzugefügt. Das Entfernen eines Elements erfolgt von oben aus dem Stapel. Im vorherigen Beispiel wird der Typ
Stack deklariert, der den Speicher und das Verhalten für einen Stapel definiert. Sie können eine Variable
deklarieren, die auf eine Instanz des Typs Stack verweist, um diese Funktionalität zu verwenden.
Assemblys enthalten ausführbaren Code in Form von Zwischensprachenanweisungen (Intermediate Language,
IL) und symbolischen Informationen in Form von Metadaten. Vor der Ausführen konvertiert der JIT-Compiler
(Just-In-Time) der .NET Common Language Runtime den IL-Code in einer Assembly in prozessorspezifischen
Code.
Da eine Assembly eine selbstbeschreibende Funktionseinheit mit Code und Metadaten ist, besteht in C# keine
Notwendigkeit für #include -Direktiven und Headerdateien. Die öffentlichen Typen und Member, die in einer
bestimmten Assembly enthalten sind, werden einfach durch Verweisen auf die Assembly beim Kompilieren des
Programms in einem C#-Programm verfügbar gemacht. Dieses Programm verwendet z.B. die
Acme.Collections.Stack -Klasse aus der acme.dll -Assembly:
using System;
using Acme.Collections;
class Example
{
public static void Main()
{
var s = new Stack<int>();
s.Push(1); // stack contains 1
s.Push(10); // stack contains 1, 10
s.Push(100); // stack contains 1, 10, 100
Console.WriteLine(s.Pop()); // stack contains 1, 10
Console.WriteLine(s.Pop()); // stack contains 1
Console.WriteLine(s.Pop()); // stack is empty
}
}
Um dieses Programm zu kompilieren, müssen Sie auf die Assembly verweisen, die die im vorherigen Beispiel
definierte Stapelklasse enthält.
C#-Programme können in mehreren Quelldateien gespeichert werden. Bei der Kompilierung eines C#-
Programms werden alle Quelldateien zusammen verarbeitet. Die Quelldateien können frei aufeinander
verweisen. Konzeptionell ist das Ganze so, als ob alle Quelldateien vor der Verarbeitung zu einer großen Datei
verkettet worden wären. Vorwärtsdeklarationen sind in C# nie erforderlich, da die Reihenfolge der Deklaration
mit wenigen Ausnahmen unbedeutend ist. C# beschränkt eine Quelldatei weder auf die Deklaration eines
einzigen öffentlichen Typs, noch muss der Name der Quelldatei mit einem in der Quelldatei deklarierten Typ
übereinstimmen.
In weiteren Artikeln in dieser Einführung werden diese Organisationsblöcke erläutert.
N Ä C H S TE
Typen und Member
04.11.2021 • 6 minutes to read
Als objektorientierte Sprache unterstützt C# die Konzepte der Kapselung, Vererbung und Polymorphie. Eine
Klasse kann direkt von einer übergeordneten Klasse erben und eine beliebige Anzahl von Schnittstellen
implementieren. Methoden, die virtuelle Methoden in einer übergeordneten Klasse überschreiben, erfordern das
override -Schlüsselwort als Möglichkeit, eine versehentliche Neudefinition zu verhindern. In C# verhält sich
eine Struktur wie eine vereinfachte Klasse. Sie entspricht einem auf dem Stapel reservierten Typ, der
Schnittstellen implementieren kann, jedoch keine Vererbung unterstützt. C# bietet record class - und
record struct -Typen. Der Zweck dieser Typen besteht primär darin, Datenwerte zu speichern.
Instanzen von Klassen werden mit dem new -Operator erstellt. Dieser reserviert Speicher für eine neue Instanz,
ruft einen Konstruktor zum Initialisieren der Instanz auf und gibt einen Verweis auf die Instanz zurück. Mit den
folgenden Anweisungen werden zwei Point -Objekte erstellt und Verweise auf diese Objekte in zwei Variablen
gespeichert:
Der von einem Objekt belegte Speicher wird automatisch wieder freigegeben, wenn das Objekt nicht mehr
erreichbar ist. Es ist weder erforderlich noch möglich, die Zuweisung von Objekten in C# explizit aufzuheben.
Typparameter
Generische Typparameter definieren Typparameter . Typparameter sind eine Liste von Typparameternamen, die
in spitzen Klammern enthalten sind. Typparameter folgen auf den Klassennamen. Die Typparameter können
dann im Körper der Klassendeklarationen zum Definieren der Klassenmember verwendet werden. Im folgenden
Beispiel lauten die Typparameter von Pair``TFirst und TSecond :
Ein Klassentyp, der zum Akzeptieren von Typparametern deklariert wird, wird als generischer Klassentyp
bezeichnet. Struktur-, Schnittstellen- und Delegattypen können auch generisch sein. Wenn die generische Klasse
verwendet wird, müssen für jeden der Typparameter Typargumente angegeben werden:
Ein generischer Typ, für den Typargumente angegeben wurden (siehe Pair<int,string> oben), wird als
konstruierter Typ bezeichnet.
Basisklassen
Mit einer Klassendeklaration kann eine Basisklasse angegeben werden. Fügen Sie nach dem Klassennamen und
den Typparametern einen Doppelpunkt und den Namen der Basisklasse ein. Das Auslassen einer
Basisklassenspezifikation ist dasselbe wie eine Ableitung vom Typ object . Im folgenden Beispiel ist Point die
Basisklasse von Point3D . Im folgenden ersten Beispiel ist object die Basisklasse von Point :
Eine Klasse erbt die Member der zugehörigen Basisklasse. Vererbung bedeutet, dass eine Klasse implizit nahezu
alle Member der Basisklasse enthält. Eine Klasse erbt die Instanz- und statischen Konstruktoren sowie den
Finalizer nicht. Eine abgeleitete Klasse kann den geerbten Membern neue Member hinzufügen, aber die
Definition eines geerbten Members kann nicht entfernt werden. Im vorherigen Beispiel erbt Point3D die
Member X und Y von Point , und alle Point3D -Instanzen enthalten die drei Eigenschaften X , Y und Z .
Ein Klassentyp kann implizit in einen beliebigen zugehörigen Basisklassentyp konvertiert werden. Eine Variable
eines Klassentyps kann auf eine Instanz der Klasse oder eine Instanz einer beliebigen abgeleiteten Klasse
verweisen. Beispielsweise kann in den vorherigen Klassendeklarationen eine Variable vom Typ Point entweder
auf Point oder auf Point3D verweisen:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
Strukturen
Klassen definieren Typen, die die Vererbung und die Polymorphie unterstützen. Sie ermöglichen es Ihnen,
komplexe Verhaltensweise anhand von Hierarchien abgeleiteter Klassen zu erstellen. Im Gegensatz dazu sind
Struktur typen (struct) einfachere Typen, deren Hauptaufgabe darin besteht, Datenwerte zu speichern.
Strukturen können keinen Basistyp deklarieren, sie leiten implizit von System.ValueType ab. Sie können keine
anderen struct -Typen von einem struct -Typ ableiten. Sie werden implizit versiegelt.
Schnittstellen
Eine *Schnittstelle _ definiert einen Vertrag, der von Klassen und Strukturen implementiert werden kann. Sie
definieren eine Schnittstelle, um Funktionen zu deklarieren, die von verschiedenen Typen gemeinsam genutzt
werden. Die System.Collections.Generic.IEnumerable<T>-Schnittstelle definiert beispielsweise eine konsistente
Methode zum Durchlaufen aller Elemente in einer Sammlung, z. B. in einem Array. Eine Schnittstelle kann
Methoden, Eigenschaften, Ereignisse und Indexer enthalten. Eine Schnittstelle stellt in der Regel keine
Implementierungen der von ihr definierten Member bereit. Sie gibt lediglich die Member an, die von Klassen
oder Strukturen bereitgestellt werden müssen, die die Schnittstelle implementieren.
Schnittstellen können Mehrfachvererbung einsetzen. Im folgenden Beispiel erbt die Schnittstelle IComboBox
sowohl von ITextBox als auch IListBox .
interface IControl
{
void Paint();
}
Klassen und Strukturen können mehrere Schnittstellen implementieren. Im folgenden Beispiel implementiert die
Klasse EditBox sowohl IControl als auch IDataBound .
interface IDataBound
{
void Bind(Binder b);
}
Wenn eine Klasse oder Struktur eine bestimmte Schnittstelle implementiert, können Instanzen dieser Klasse
oder Struktur implizit in diesen Schnittstellentyp konvertiert werden. Beispiel:
Enumerationen
Enumerationstypen (Enum) definieren eine Gruppe konstanter Werte. Mit dem folgenden enum -Typ werden
Konstanten deklariert, die verschiedene Wurzelgemüsesorten definieren:
Sie können einen enum -Typ definieren, der in Kombination als Flags verwendet werden sollen. In der folgenden
Deklaration werden Flags für die vier Jahreszeiten deklariert. Jede Kombination der Jahreszeiten kann
angewendet werden, einschließlich eines All -Werts, der alle Jahreszeiten umfasst:
[Flags]
public enum Seasons
{
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
All = Summer | Autumn | Winter | Spring
}
Nullable-Typen
Jede Art von Variable kann als non-nullable _ oder _nullable_ deklariert werden. Eine Nullable-Variable kann
einen zusätzlichen null -Wert enthalten, der angibt, dass kein Wert vorliegt. Nullable-Werttypen (Strukturen
oder Enumerationen) werden mit System.Nullable<T> dargestellt. Non-Nullable- und Nullable-Verweistypen
werden beide vom zugrunde liegenden Verweistyp repräsentiert. Der Unterschied wird von Metadaten
dargestellt, die vom Compiler und einigen Bibliotheken gelesen werden. Der Compiler gibt Warnungen aus,
wenn Nullable-Verweise dereferenziert werden, ohne dass ihr Wert zunächst auf null geprüft wird. Der
Compiler gibt auch Warnungen aus, wenn Non-Nullable-Verweisen ein Wert zugewiesen wird, der null sein
kann. Im folgenden Beispiel wird ein _nullable int_-Wert deklariert und mit null initialisiert. Dann wird der
Wert auf 5 festgelegt. Dasselbe Konzept wird mit _nullable string** veranschaulicht. Weitere Informationen
finden Sie unter Nullable-Werttypen und Nullable-Verweistypen.
Tupel
C# unterstützt Tupel , die eine kompakte Syntax zum Gruppieren mehrerer Datenelemente in einer einfachen
Datenstruktur bereitstellen. Sie können ein Tupel instanziieren, indem Sie die Typen und Namen der Member
zwischen ( und ) deklarieren. Dies wird im folgenden Beispiel veranschaulicht:
Tupel bieten eine Alternative für Datenstrukturen mit mehreren Membern, ohne dass die Bausteine verwendet
werden müssen, die im nächsten Artikel beschrieben werden.
ZU RÜ CK W E ITE R
Programmbausteine
04.11.2021 • 23 minutes to read
Die im vorherigen Artikel beschriebenen Typen werden mithilfe der folgenden Bausteine erstellt: *Member _,
Ausdrücke und _ *Anweisungen**.
Member
Die Member von class sind entweder statische Member _ oder _Instanzmember**. Statische Member
gehören zu Klassen, Instanzmember gehören zu Objekten (Instanzen von Klassen).
In der folgenden Liste finden Sie einen Überblick über die Memberarten, die eine Klasse enthalten kann.
Konstanten: Konstante Werte, die der Klasse zugeordnet sind
Felder : Variablen, die der Klasse zugeordnet sind.
Methods (Methoden): Aktionen, die von der Klasse ausgeführt werden
Proper ties: Aktionen im Zusammenhang mit dem Lesen und Schreiben von benannten Eigenschaften der
Klasse
Indexer : Aktionen im Zusammenhang mit dem Indizieren von Instanzen der Klasse, z.B. einem Array
Ereignisse: Benachrichtigungen, die von der Klasse generiert werden können
Operatoren: Operatoren für Konvertierungen und Ausdrücke, die von der Klasse unterstützt werden
Konstruktoren: Aktionen, die zum Initialisieren von Instanzen der Klasse oder der Klasse selbst benötigt
werden
Finalizer : Aktionen, die ausgeführt werden, bevor Instanzen der Klasse dauerhaft verworfen werden
Typen: Geschachtelte Typen, die von der Klasse deklariert werden
Barrierefreiheit
Jeder Member einer Klasse ist mit einem Zugriff verknüpft, der die Regionen des Programmtexts steuert, die auf
den Member zugreifen können. Es gibt sechs mögliche Formen des Zugriffs. Die Zugriffsmodifizierer werden im
Folgenden zusammengefasst.
public : Der Zugriff ist nicht eingeschränkt.
private : Der Zugriff ist auf diese Klasse beschränkt.
protected : Der Zugriff ist auf diese Klasse oder von dieser abgeleiteten Klassen beschränkt.
internal : Der Zugriff ist auf die aktuelle Assembly ( .exe oder .dll ) beschränkt.
protected internal : Der Zugriff ist auf diese Klasse, auf Klassen, die von dieser Klasse abgeleitet wurden,
oder auf Klassen innerhalb der gleichen Assembly beschränkt.
private protected : Der Zugriff ist auf diese Klasse und auf Klassen in derselben Assembly beschränkt, die
von diesem Typ abgeleitet wurden.
Felder
Ein Feld ist eine Variable, die einer Klasse oder einer Instanz einer Klasse zugeordnet ist.
Ein Feld, das mit dem static-Modifizierer deklariert wurde, definiert ein statisches Feld. Ein statisches Feld
identifiziert genau einen Speicherort. Unabhängig davon, wie viele Instanzen einer Klasse erstellt werden, gibt es
immer nur eine Kopie eines statischen Felds.
Ein Feld, das ohne den static-Modifizierer deklariert wurde, definiert ein Instanzfeld. Jede Instanz einer Klasse
enthält eine separate Kopie aller Instanzfelder dieser Klasse.
Im folgenden Beispiel weist jede Instanz der Color -Klasse eine separate Kopie der Instanzfelder R , G und B
auf, aber es gibt nur eine Kopie der statischen Felder Black , White , Red , Green und Blue :
public byte R;
public byte G;
public byte B;
Wie im vorherigen Beispiel gezeigt, können schreibgeschützte Felder mit einem readonly -Modifizierer
deklariert werden. Zuweisungen zu einem schreibgeschützten Feld können nur im Rahmen der Deklaration des
Felds oder in einem Konstruktor derselben Klasse auftreten.
Methoden
Eine Methode ist ein Member, das eine Berechnung oder eine Aktion implementiert, die durch ein Objekt oder
eine Klasse durchgeführt werden kann. Auf statische Methoden wird über die Klasse zugegriffen. Auf
Instanzmethoden wird über Instanzen der Klasse zugegriffen.
Methoden verfügen über eine Liste von Parametern, die Werte oder Variablenverweise darstellen, die an die
Methode übergeben werden. Methoden besitzen einen Rückgabetyp, der den Typ des Werts festlegt, der von
der Methode berechnet und zurückgegeben wird. Der Rückgabetyp einer Methode lautet void , wenn kein Wert
zurückgegeben wird.
Ebenso wie Typen können Methoden einen Satz an Typparametern aufweisen, für den beim Aufruf der Methode
Typargumente angegeben werden müssen. Im Gegensatz zu Typen können die Typargumente häufig aus den
Argumenten eines Methodenaufrufs abgeleitet werden und müssen nicht explizit angegeben werden.
Die Signatur einer Methode muss innerhalb der Klasse eindeutig sein, in der die Methode deklariert ist. Die
Signatur einer Methode besteht aus dem Namen der Methode, der Anzahl von Typparametern und der Anzahl,
den Modifizierern und den Typen der zugehörigen Parameter. Die Signatur einer Methode umfasst nicht den
Rückgabetyp.
Wenn es sich bei einem Methodenkörper um einen einzelnen Ausdruck handelt, kann die Methode mithilfe
eines kompakten Ausdrucksformat definiert werden. Dies wird im folgenden Beispiel veranschaulicht:
Parameter
Parameter werden dazu verwendet, Werte oder Variablenverweise an Methoden zu übergeben. Die Parameter
einer Methode erhalten ihre tatsächlichen Werte über Argumente, die angegeben werden, wenn die Methode
aufgerufen wird. Es gibt vier Arten von Parametern: Wertparameter, Verweisparameter, Ausgabeparameter und
Parameterarrays.
Ein Wertparameter wird zum Übergeben von Eingabeargumenten verwendet. Ein Wertparameter entspricht
einer lokalen Variablen, die ihren Anfangswert von dem Argument erhält, das für den Parameter übergeben
wurde. Änderungen an einem Wertparameter wirken sich nicht auf das Argument aus, das für den Parameter
übergeben wurde.
Wertparameter können optional sein, indem ein Standardwert festgelegt wird, damit die zugehörigen
Argumente weggelassen werden können.
Ein Verweisparameter wird zum Übergeben von Argumenten als Verweis verwendet. Das für einen
Verweisparameter übergebene Argument muss eine Variable mit einem definitiven Wert sein. Währen der
Ausführung der Methode stellt der Verweisparameter denselben Speicherort wie die Argumentvariable dar. Ein
Verweisparameter wird mit dem ref -Modifizierer deklariert. Das folgende Beispiel veranschaulicht die
Verwendung des ref -Parameters.
Ein Ausgabeparameter wird zum Übergeben von Argumenten als Verweis verwendet. Er ist einem
Verweisparameter ähnlich, außer dass er nicht erfordert, dass Sie explizit dem vom Aufrufer bereitgestellten
Argument einen Wert zuweisen. Ein Ausgabeparameter wird mit dem out -Modifizierer deklariert. Das
folgende Beispiel zeigt die Verwendung von out -Parametern mithilfe der in C# 7 eingeführten Syntax.
static void Divide(int x, int y, out int result, out int remainder)
{
result = x / y;
remainder = x % y;
}
Ein Parameterarray ermöglicht es, eine variable Anzahl von Argumenten an eine Methode zu übergeben. Ein
Parameterarray wird mit dem params -Modifizierer deklariert. Nur der letzte Parameter einer Methode kann ein
Parameterarray sein, und es muss sich um ein eindimensionales Parameterarray handeln. Die Methoden Write
und WriteLine der Klasse System.Console sind gute Beispiele für die Nutzung eines Parameterarrays. Sie
werden folgendermaßen deklariert.
public class Console
{
public static void Write(string fmt, params object[] args) { }
public static void WriteLine(string fmt, params object[] args) { }
// ...
}
Innerhalb einer Methode mit einem Parameterarray verhält sich das Parameterarray wie ein regulärer Parameter
des Arraytyps. Beim Aufruf einer Methode mit einem Parameterarray ist es jedoch möglich, entweder ein
einzelnes Argument des Parameterarraytyps oder eine beliebige Anzahl von Argumenten des Elementtyps des
Parameterarrays zu übergeben. Im letzteren Fall wird automatisch eine Arrayinstanz erstellt und mit den
vorgegebenen Argumenten initialisiert. Dieses Beispiel:
int x, y, z;
x = 3;
y = 4;
z = 5;
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
int x = 3, y = 4, z = 5;
class Squares
{
public static void WriteSquares()
{
int i = 0;
int j;
while (i < 10)
{
j = i * i;
Console.WriteLine($"{i} x {i} = {j}");
i = i + 1;
}
}
}
In C# muss eine lokale Variable definitiv zugewiesen sein, bevor ihr Wert abgerufen werden kann. Wenn die
vorherige Deklaration von i beispielsweise keinen Anfangswert enthält, würde der Compiler bei der späteren
Verwendung von i einen Fehler melden, weil i zu diesen Zeitpunkten im Programm nicht definitiv
zugewiesen ist.
Eine Methode kann return -Anweisungen verwenden, um die Steuerung an den zugehörigen Aufrufer
zurückzugeben. In einer Methode, die void zurückgibt, können return -Anweisungen keinen Ausdruck
angeben. In einer Methode, die nicht „void“ zurückgibt, müssen return -Anweisungen einen Ausdruck enthalten,
der den Rückgabewert berechnet.
Statische Methoden und Instanzmethoden
Eine Methode, die mit einem static -Modifizierer deklariert wird, ist eine statische Methode. Eine statische
Methode führt keine Vorgänge für eine spezifische Instanz aus und kann nur direkt auf statische Member
zugreifen.
Eine Methode, die ohne einen static -Modifizierer deklariert wird, ist eine Instanzmethode. Eine
Instanzmethode führt Vorgänge für eine spezifische Instanz aus und kann sowohl auf statische Member als auch
auf Instanzmember zugreifen. Auf die Instanz, für die eine Instanzmethode aufgerufen wurde, kann explizit als
this zugegriffen werden. Es ist ein Fehler, in einer statischen Methode auf this zu verweisen.
Die folgende Entity -Klasse umfasst sowohl statische Member als auch Instanzmember.
class Entity
{
static int s_nextSerialNo;
int _serialNo;
public Entity()
{
_serialNo = s_nextSerialNo++;
}
Jede Entity -Instanz enthält eine Seriennummer (und vermutlich weitere Informationen, die hier nicht
angezeigt werden). Der Entity -Konstruktor (der einer Instanzmethode ähnelt) initialisiert die neue Instanz mit
der nächsten verfügbaren Seriennummer. Da der Konstruktor ein Instanzmember ist, kann er sowohl auf das
_serialNo -Instanzfeld als auch auf das statische s_nextSerialNo -Feld zugreifen.
Die statischen Methoden GetNextSerialNo und SetNextSerialNo können auf das statische Feld s_nextSerialNo
zugreifen, aber es wäre ein Fehler, über diese Methoden direkt auf das Instanzfeld _serialNo zuzugreifen.
Im folgenden Beispiel wird die Verwendung der Entity -Klasse veranschaulicht.
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
Die statischen Methoden SetNextSerialNo und GetNextSerialNo werden für die Klasse aufgerufen, während die
GetSerialNo -Instanzmethode für Instanzen der Klasse aufgerufen wird.
Die vorherigen vier Klassen können zum Modellieren arithmetischer Ausdrücke verwendet werden.
Beispielsweise kann mithilfe von Instanzen dieser Klassen der Ausdruck x + 3 folgendermaßen dargestellt
werden.
Die Evaluate -Methode einer Expression -Instanz wird aufgerufen, um den vorgegebenen Ausdruck
auszuwerten und einen double -Wert zu generieren. Die Methode verwendet ein Dictionary -Argument, das
Variablennamen (als Schlüssel der Einträge) und Werte (als Werte der Einträge) enthält. Da Evaluate eine
abstrakte Methode ist, müssen nicht-abstrakte Klassen, die von Expression abgeleitet sind, Evaluate außer
Kraft setzen.
Eine Implementierung von Constant für Evaluate gibt lediglich die gespeicherte Konstante zurück. Eine
Implementierung von VariableReference sucht im Wörterbuch nach dem Variablennamen und gibt den
Ergebniswert zurück. Eine Implementierung von Operation wertet zunächst (durch einen rekursiven Aufruf der
zugehörigen Evaluate -Methoden) den linken und rechten Operanden aus und führt dann die vorgegebene
arithmetische Operation aus.
Das folgende Programm verwendet die Expression -Klassen zum Auswerten des Ausdrucks x * (y + 2) für
verschiedene Werte von x und y .
Methodenüberladung
Das Überladen von Methoden macht es möglich, dass mehrere Methoden in derselben Klasse denselben Namen
verwenden, solange sie eindeutige Signaturen aufweisen. Beim Kompilieren des Aufrufs einer überladenen
Methode verwendet der Compiler die Überladungsauflösung, um die spezifische Methode zu ermitteln, die
aufgerufen werden soll. Die Überladungsauflösung ermittelt die Methode, die den Argumenten am besten
entspricht. Wenn keine optimale Übereinstimmung gefunden wird, wird ein Fehler gemeldet. Das folgende
Beispiel zeigt die Verwendung der Überladungsauflösung. Der Kommentar für jeden Aufruf in der UsageExample
-Methode zeigt, welche Methode aufgerufen wird.
class OverloadingExample
{
static void F() => Console.WriteLine("F()");
static void F(object x) => Console.WriteLine("F(object)");
static void F(int x) => Console.WriteLine("F(int)");
static void F(double x) => Console.WriteLine("F(double)");
static void F<T>(T x) => Console.WriteLine("F<T>(T)");
static void F(double x, double y) => Console.WriteLine("F(double, double)");
Wie im Beispiel gezeigt, kann eine bestimmte Methode immer ausgewählt werden, indem die Argumente
explizit in die exakten Parametertypen und Typargumente umgewandelt werden.
Andere Funktionsmember
Member, die ausführbaren Code enthalten, werden als Funktionsmember einer Klasse bezeichnet. Im vorherigen
Abschnitt wurden Methoden beschrieben, die die Haupttypen von Funktionsmembern sind. In diesem Abschnitt
werden die weiteren Funktionsmember behandelt, die C# unterstützt: Konstruktoren, Eigenschaften, Indexer,
Ereignisse, Operatoren und Finalizer.
Im folgenden Beispiel wird eine generische Klasse namens MyList<T> gezeigt, die eine wachsende Liste von
Objekten implementiert. Die Klasse enthält verschiedene Beispiele der gängigsten Arten von
Funktionsmembern.
T[] _items;
int _count;
Konstruktoren
C# unterstützt sowohl Instanzkonstruktoren als auch statische Konstruktoren. Ein Instanzkonstruktor ist ein
Member, der die erforderlichen Aktionen zum Initialisieren einer Instanz einer Klasse implementiert. Ein
statischer Konstruktor ist ein Member, der die zum Initialisieren einer Klasse erforderlichen Aktionen
implementiert, um die Klasse beim ersten Laden selbst zu initialisieren.
Ein Konstruktor wird wie eine Methode ohne Rückgabetyp und mit demselben Namen wie die enthaltende
Klasse deklariert. Wenn eine Konstruktordeklaration einen static -Modifizierer enthält, deklariert diese einen
statischen Konstruktor. Andernfalls wird ein Instanzkonstruktor deklariert.
Instanzkonstruktoren können überladen werden und optionale Parameter verwenden. Die MyList<T> -Klasse
deklariert z.B. einen Instanzkonstruktor mit einem einzelnen optionalen int -Parameter. Instanzkonstruktoren
werden über den new -Operator aufgerufen. Die folgenden Anweisungen weisen zwei Instanzen von
MyList<string> unter Verwendung des Konstruktors der MyList -Klasse zu, mit dem optionalen Argument und
ohne das optionale Argument.
MyList<string> list1 = new MyList<string>();
MyList<string> list2 = new MyList<string>(10);
Im Gegensatz zu anderen Membern werden Instanzkonstruktoren nicht geerbt. Eine Klasse weist keine anderen
Instanzkonstruktoren auf als diejenigen, die tatsächlich in der Klasse deklariert wurden. Wenn kein
Instanzkonstruktor für eine Klasse angegeben ist, wird automatisch ein leerer Instanzkonstruktor ohne
Parameter bereitgestellt.
Eigenschaften
Eigenschaften sind eine natürliche Erweiterung der Felder. Beide sind benannte Member mit zugeordneten
Typen, und für den Zugriff auf Felder und Eigenschaften wird dieselbe Syntax verwendet. Im Gegensatz zu
Feldern geben Eigenschaften jedoch keine Speicherorte an. Stattdessen verfügen Eigenschaften über
Zugriffsmethoden, die die ausgeführten Anweisungen angeben, wenn ihre Werte gelesen oder geschrieben
werden. Ein get-Accessor liest den Wert. Ein set-Accessor schreibt den Wert.
Eine Eigenschaft wird wie ein Feld deklariert, abgesehen davon, dass die Deklaration nicht auf ein Semikolon,
sondern auf einen get- oder set-Accessor endet, der von den Trennzeichen { und } umschlossen wird. Eine
Eigenschaft, die eine Get-Zugriffsmethode und eine Set-Zugriffsmethode umfasst, ist eine Eigenschaft mit Lese-
/Schreibzugriff. Eine Eigenschaft, die nur eine Get-Zugriffsmethode umfasst, ist eine schreibgeschützte
Eigenschaft. Eine Eigenschaft, die nur eine Set-Zugriffsmethode umfasst, ist eine lesegeschützte Eigenschaft.
Ein get-Accessor entspricht einer Methode ohne Parameter mit einem Rückgabewert des Eigenschaftstyps. Ein
set-Accessor entspricht einer Methode mit einem einzigen Parameter namens „value“ ohne Rückgabetyp. Die
get-Zugriffsmethode berechnet den Wert der Eigenschaft. Die set-Zugriffsmethode stellt einen neuen Wert für
die Eigenschaft bereit. Wenn die Eigenschaft das Ziel einer Zuweisung oder der Operand von ++ oder -- ist,
wird die set-Zugriffsmethode aufgerufen. In anderen Fällen, in denen die Eigenschaft referenziert wird, wird die
get-Zugriffsmethode aufgerufen.
Die MyList<T> -Klasse deklariert die beiden Eigenschaften „ Count “ und „ Capacity “, von denen die eine
schreibgeschützt ist und die andere Lese- und Schreibzugriff besitzt. Im folgenden Beispielcode wird die
Verwendung dieser Eigenschaften veranschaulicht:
Ähnlich wie bei Feldern und Methoden unterstützt C# sowohl Instanzeigenschaften als auch statische
Eigenschaften. Statische Eigenschaften werden mit dem static-Modifizierer, Instanzeigenschaften werden ohne
static-Modifizierer deklariert.
Die Accessors einer Eigenschaft können virtuell sein. Wenn eine Eigenschaftendeklaration einen virtual -,
abstract - oder override -Modifizierer enthält, wird dieser auf den Accessor der Eigenschaft angewendet.
Indexer
Ein Indexer ist ein Member, mit dem Objekte wie ein Array indiziert werden können. Ein Indexer wird wie eine
Eigenschaft deklariert, abgesehen davon, dass der Name des Members this ist, gefolgt von einer
Parameterliste, die zwischen die Trennzeichen [ und ] geschrieben wird. Die Parameter stehen im Accessor
des Indexers zur Verfügung. Ähnlich wie Eigenschaften können Indexer Lese-/Schreibzugriff besitzen,
schreibgeschützt und lesegeschützt sein und virtuelle Accessors verwenden.
Die MyList<T> -Klasse deklariert einen einzigen Indexer mit Lese-/Schreibzugriff, der einen int -Parameter
akzeptiert. Der Indexer ermöglicht es, Instanzen von MyList<T> mit int -Werten zu indizieren. Beispiel:
MyList<string> names = new MyList<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
string s = names[i];
names[i] = s.ToUpper();
}
Indexer können überladen werden. Eine Klasse kann mehrere Indexer deklarieren, solange sich die Anzahl oder
die Typen ihrer Parameter unterscheiden.
Events
Ein Ereignis ist ein Member, der es einer Klasse oder einem Objekt ermöglicht, Benachrichtigungen
bereitzustellen. Ein Ereignis wird wie ein Feld deklariert, abgesehen davon, dass es ein event -Schlüsselwort
enthält und einen Delegattyp aufweisen muss.
Innerhalb einer Klasse, die einen Ereignismember deklariert, verhält sich das Ereignis wie ein Feld des
Delegattyps (vorausgesetzt, das Ereignis ist nicht abstrakt und deklariert keine Zugriffsmethoden). Das Feld
speichert einen Verweis auf einen Delegaten, der die Ereignishandler repräsentiert, die dem Ereignis
hinzugefügt wurden. Wenn keine Ereignishandler vorhanden sind, ist das Feld null .
Die MyList<T> -Klasse deklariert einen einzigen Ereignismember namens Changed , der angibt, dass der Liste ein
neues Element hinzugefügt wurde. Das Changed-Ereignis wird durch die virtuelle Methode OnChanged
ausgelöst, die zunächst prüft, ob das Ereignis null ist (d.h. nicht über Handler verfügt). Das Auslösen eines
Ereignisses entspricht exakt dem Aufrufen des Delegats, der durch das Ereignis repräsentiert wird. Es gibt keine
speziellen Sprachkonstrukte zum Auslösen von Ereignissen.
Clients reagieren über Ereignishandler auf Ereignisse. Ereignishandler werden unter Verwendung des += -
Operators angefügt und mit dem -= -Operator entfernt. Im folgenden Beispiel wird dem Changed -Ereignis von
MyList<string> ein Ereignishandler hinzugefügt.
class EventExample
{
static int s_changeCount;
In fortgeschrittenen Szenarios, in denen die zugrunde liegende Speicherung eines Ereignisses gesteuert werden
soll, kann eine Ereignisdeklaration explizit die Zugriffsmethoden add und remove bereitstellen, die der
Zugriffsmethode set einer Eigenschaft ähneln.
Operatoren
Ein Operator ist ein Member, der die Bedeutung der Anwendung eines bestimmten Ausdrucksoperators auf
Instanzen einer Klasse definiert. Es können drei Arten von Operatoren definiert werden: unäre Operatoren,
binäre Operatoren und Konvertierungsoperatoren. Alle Operatoren müssen als public und static deklariert
werden.
Die MyList<T> -Klasse deklariert zwei Operatoren: operator == und operator != . Diese überschriebenen
Operatoren verleihen Ausdrücken eine neue Bedeutung, die diese Operatoren auf MyList -Instanzen anwenden.
Insbesondere die Operatoren definieren die Gleichheit für zwei Instanzen von MyList<T> , indem alle
enthaltenen Objekte mithilfe ihrer Equals -Methoden verglichen werden. Im folgenden Beispiel wird der == -
Operator verwendet, um zwei Instanzen von MyList<int> zu vergleichen.
Die erste Methode Console.WriteLine gibt True aus, weil die zwei Listen dieselbe Anzahl von Objekten mit
denselben Werten in derselben Reihenfolge enthalten. Wenn MyList<T> nicht operator == definieren würde,
würde die Ausgabe der ersten Console.WriteLine -Methode False lauten, weil a und b auf unterschiedliche
MyList<int> -Instanzen verweisen.
Finalizer
Ein Finalizer ist ein Member, der die erforderlichen Aktionen zum Bereinigen einer Instanz einer Klasse
implementiert. In der Regel ist ein Finalizer erforderlich, um nicht verwaltete Ressourcen freizugeben. Finalizer
können weder Parameter noch Zugriffsmodifizierer aufweisen und können nicht explizit aufgerufen werden. Der
Finalizer für eine Instanz wird bei der Garbagecollection automatisch aufgerufen. Weitere Informationen finden
Sie im Artikel zu Finalizern.
Der Garbage Collector kann weitestgehend selbst über den Zeitpunkt der Objektbereinigung und die
Ausführung der Finalizer entscheiden. Insbesondere der Zeitpunkt für den Aufruf der Finalizer ist nicht
festgelegt, und Finalizer können für beliebige Threads ausgeführt werden. Aus diesen und weiteren Gründen
sollten Klassen Finalizer nur dann implementieren, wenn keine andere Lösung möglich ist.
Die using -Anweisung bietet einen besseren Ansatz für die Objektzerstörung.
Ausdrücke
Ausdrücke bestehen aus Operanden und Operatoren. Die Operatoren eines Ausdrucks geben an, welche
Operationen auf die Operanden angewendet werden. Beispiele für Operatoren sind + , - , * , / und new .
Beispiele für Operanden sind Literale, Felder, lokale Variablen und Ausdrücke.
Wenn ein Ausdruck mehrere Operatoren enthält, steuert die Rangfolge der Operatoren die Reihenfolge, in der
die einzelnen Operatoren ausgewertet werden. Der Ausdruck x + y * z wird z.B. als x + (y * z) ausgewertet,
da der * -Operator Vorrang vor dem + -Operator hat.
Tritt ein Operand zwischen zwei Operatoren mit gleicher Rangfolge auf, steuert die Assoziativität der Operatoren
die Reihenfolge, in der die Vorgänge ausgeführt werden:
Mit Ausnahme der Zuweisungs- und NULL-Sammeloperatoren sind alle binären Operatoren linksassoziativ,
was bedeutet, dass Vorgänge von links nach rechts ausgeführt werden. x + y + z wird beispielsweise als
(x + y) + z ausgewertet.
Die Zuweisungsoperatoren, die NULL-Sammeloperatoren ?? und ??= und der bedingte Operator ?: sind
rechtsassoziativ, d.h., die Operationen werden von rechts nach links ausgeführt. x = y = z wird
beispielsweise als x = (y = z) ausgewertet.
Rangfolge und Assoziativität können mit Klammern gesteuert werden. In x + y * z wird beispielsweise zuerst
y mit z multipliziert und dann das Ergebnis zu x addiert, aber in (x + y) * z werden zunächst x und y
addiert, und dann wird das Ergebnis mit z multipliziert.
Die meisten Operatoren können überladen werden. Das Überladen von Operatoren ermöglicht die Angabe
benutzerdefinierter Operatorimplementierungen für Vorgänge, in denen einer der Operanden oder beide einer
benutzerdefinierten Klasse oder einem benutzerdefinierten Strukturtyp angehören.
C# bietet Operatoren für arithmetische, logische, bitweise und Verschiebungsvorgänge sowie Vergleiche von
Gleichheit und Reihenfolge.
Die vollständige Liste der nach Rangfolgenebene sortierten C#-Operatoren finden Sie unter C#-Operatoren.
Anweisungen
Die Aktionen eines Programms werden mit Anweisungen ausgedrückt. C# unterstützt verschiedene Arten von
Anweisungen, von denen ein Teil als eingebettete Anweisungen definiert ist.
Ein Block ermöglicht, mehrere Anweisungen in Kontexten zu schreiben, in denen eine einzelne Anweisung
zulässig ist. Ein Block besteht aus einer Liste von Anweisungen, die zwischen den Trennzeichen { und }
geschrieben sind.
Deklarationsanweisungen werden verwendet, um lokale Variablen und Konstanten deklarieren.
Ausdrucksanweisungen werden zum Auswerten von Ausdrücken verwendet. Ausdrücke, die als
Anweisungen verwendet werden können, enthalten Methodenaufrufe, Objektzuordnungen mit dem new -
Operator, Zuweisungen mit = und den Verbundzuweisungsoperatoren, Inkrementier- und
Dekrementiervorgänge unter Verwendung des ++ - und -- -Operators und await -Ausdrücke.
Auswahlanweisungen werden verwendet, um eine Anzahl von möglichen Anweisungen für die Ausführung
anhand des Werts eines Ausdrucks auszuwählen. Diese Gruppe enthält die Anweisungen if und switch .
Iterationsanweisungen werden verwendet, um eine eingebettete Anweisung wiederholt auszuführen. Diese
Gruppe enthält die Anweisungen while , do , for und foreach .
Sprunganweisungen werden verwendet, um die Steuerung zu übertragen. Diese Gruppe enthält die
Anweisungen break , continue , goto , throw , return und yield .
Mit der try ... catch -Anweisung werden Ausnahmen abgefangen, die während der Ausführung eines Blocks
auftreten, und mit der try ... finally -Anweisung wird Finalisierungscode angegeben, der immer
ausgeführt wird, unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht.
Mit den Anweisungen checked und unchecked wird der Überlaufüberprüfungs-Kontext für arithmetische
Operationen für Ganzzahltypen und Konvertierungen gesteuert.
Die lock -Anweisung wird verwendet, um die Sperre für gegenseitigen Ausschluss für ein bestimmtes
Objekt abzurufen, eine Anweisung auszuführen und die Sperre aufzuheben.
Die using -Anweisung wird verwendet, um eine Ressource abzurufen, eine Anweisung auszuführen und
dann diese Ressource zu verwerfen.
In der folgenden Liste werden die Arten von Anweisungen aufgeführt, die verwendet werden können:
Deklaration lokaler Variablen
Deklaration lokaler Konstanten
Ausdrucksanweisung
if -Anweisung
switch -Anweisung
while -Anweisung
do -Anweisung
for -Anweisung
foreach -Anweisung
break -Anweisung
continue -Anweisung
goto -Anweisung
return -Anweisung
yield -Anweisung
throw -Anweisungen und try -Anweisungen
checked - und unchecked -Anweisungen
lock -Anweisung
using -Anweisung
ZU RÜ CK W E ITE R
Wichtige Sprachbereiche
04.11.2021 • 8 minutes to read
Mit diesem Beispiel wird ein eindimensionales Array erstellt und verwendet. C# unterstützt auch
mehrdimensionale Arrays. Die Anzahl von Dimensionen eines Arraytyps, auch als Rang des Arraytyps
bezeichnet, ist 1 plus die Anzahl von Kommas, die innerhalb der eckigen Klammern des Arraytyps angegeben ist.
Mit dem folgenden Beispiel wird respektive ein eindimensionales, ein zweidimensionales und ein
dreidimensionales Array erstellt.
Das a1 -Array enthält 10 Elemente, das a2 -Array umfasst 50 (10 × 5) Elemente, und das a3 -Array enthält 100
(10 × 5 × 2) Elemente. Ein Array kann einen beliebigen Elementtyp verwenden, einschließlich eines Arraytyps.
Ein Array mit Elementen eines Arraytyps wird auch als Jagged Array bezeichnet, weil die Länge der
Elementarrays nicht identisch sein muss. Im folgenden Beispiel wird ein Array aus int -Arrays zugewiesen:
In der ersten Zeile wird ein Array mit drei Elementen erstellt, das jeweils den Typ int[] und einen Anfangswert
von null aufweist. In den nachfolgenden Zeilen werden die drei Elemente mit Verweisen auf einzelne
Arrayinstanzen unterschiedlicher Länge initialisiert.
Der new -Operator erlaubt es, die Anfangswerte der Arrayelemente unter Verwendung eines
Arrayinitialisierers anzugeben, bei dem es sich um eine Liste von Ausdrücken zwischen den Trennzeichen {
und } handelt. Mit dem folgenden Beispiel wird ein int[] mit drei Elementen zugewiesen und initialisiert.
Die Länge des Arrays wird von der Anzahl der Ausdrücke zwischen { und } abgeleitet. Die Arrayinitialisierung
kann weiter so verkürzt werden, dass der Arraytyp nicht erneut angegeben werden muss.
int[] a = { 1, 2, 3 };
Die foreach -Anweisung kann für die Enumeration der Elemente von beliebigen Sammlungen verwendet
werden. Mit dem folgenden Code wird das Array aus dem vorherigen Beispiel aufgezählt:
Die foreach -Anweisung nutzt die IEnumerable<T>-Schnittstelle und kann daher mit jeder Sammlung
funktionieren.
Zeichenfolgeninterpolierung
Die Zeichenfolgeninterpolation von C# ermöglicht Ihnen das Formatieren von Zeichenfolgen durch
Definieren von Ausdrücken, deren Ergebnisse in eine Formatzeichenfolge platziert werden. Im folgenden
Beispiel wird die Temperatur für einen jeweiligen Tag aus Wetterdaten ausgegeben:
Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-DD-YYYY}");
Console.WriteLine($" was {weatherData.LowTemp} and {weatherData.HighTemp}.");
// Output (similar to):
// The low and high temperature on 08-11-2020
// was 5 and 30.
Eine interpolierte Zeichenfolge wird mithilfe des $ -Tokens deklariert. Die Zeichenfolgeninterpolation wertet die
Ausdrücke zwischen { und } aus, konvertiert das Ergebnis in string und ersetzt den Text zwischen den
Klammern durch das Zeichenfolgenergebnis des Ausdrucks. Mit : im ersten Ausdruck gibt
{weatherData.Date:MM-DD-YYYY} die Formatzeichenfolge an. Im obigen Beispiel wird festgelegt, dass das Datum
im Format „MM-TT-JJJJ“ ausgegeben werden soll.
Musterabgleich
Die C#-Sprache stellt Musterabgleichsausdrücke bereit, um den Zustand eines Objekts abzufragen und Code
auf Grundlage dieses Zustands auszuführen. Sie können Typen und Werte von Eigenschaften und Feldern
untersuchen, um zu bestimmen, welche Aktion ausgeführt werden soll. Der switch -Ausdruck ist der primäre
Ausdruck für den Musterabgleich.
class Multiplier
{
double _factor;
class DelegateExample
{
static double[] Apply(double[] a, Function f)
{
var result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
Eine Instanz des Delegattyps Function kann auf jede Methode verweisen, die ein double -Argument und einen
double -Wert akzeptiert. Die Apply -Methode wendet eine jeweilige Function (Funktion) auf die Elemente einer
double[] -Methode an. Mit den Ergebnissen wird eine double[] -Methode zurückgegeben. In der Main -
Methode wird Apply verwendet, um drei verschiedene Funktionen auf ein double[] anzuwenden.
Ein Delegat kann entweder auf eine statische Methode verweisen (z.B. Square oder Math.Sin im vorherigen
Beispiel) oder eine Instanzmethode (z.B. m.Multiply im vorherigen Beispiel). Ein Delegat, der auf eine
Instanzmethode verweist, verweist auch auf ein bestimmtes Objekt, und wenn die Instanzmethode durch den
Delegaten aufgerufen wird, wird das Objekt this im Aufruf.
Delegaten können auch mithilfe anonymer Funktionen erstellt werden, bei denen es sich um „Inlinemethoden“
handelt, die bei der Deklaration erstellt werden. Anonyme Funktionen können die lokalen Variablen der
umgebenden Methoden sehen. Im folgenden Beispiel wird keine Klasse erstellt:
Ein Delegat weiß nicht und interessiert sich nicht dafür, welche Klasse der Methode er referenziert. Die Methode,
auf die verwiesen wird, muss über die gleichen Parameter und denselben Rückgabetyp wie der Delegat
verfügen.
async/await
C# unterstützt asynchrone Programme mit zwei Schlüsselwörtern: async und await . Sie fügen den async -
Modifizierer zu einer Methodendeklaration hinzu, um zu deklarieren, dass die Methode asynchron ist. Der
await -Operator weist den Compiler an, asynchron auf den Ergebnis zu warten. Die Kontrolle wird wieder dem
Aufrufer überlassen, und die Methode gibt eine Struktur zurück, die den Zustand der asynchronen Arbeitet
verwaltet. In der Regel handelt es sich um eine System.Threading.Tasks.Task<TResult>-Struktur, es kann jedoch
ein beliebiger Typ vorliegen, der das await-Muster unterstützt. Diese Features ermöglichen Ihnen das Schreiben
von Code, der sich wie das synchrone Gegenstück liest, aber asynchron ausgeführt wird. Mit dem folgenden
Code wird beispielsweise die Homepage für die Microsoft-Dokumentation heruntergeladen:
In diesem kleinen Beispiel werden die wichtigsten Features für die asynchrone Programmierung
veranschaulicht:
Die Methodendeklaration enthält den Modifizierer async .
Der Text der Methode await enthält die Rückgabe der GetByteArrayAsync -Methode.
Der in der return -Anweisung angegebene Typ stimmt mit dem Typargument in der Task<T> -Deklaration
für die Methode überein. (Eine Methode, die ein Task zurückgibt, würde return -Anweisungen ohne
Argumente verwenden.)
Attributes
Typen, Member und andere Entitäten in einem C#-Programm unterstützen Modifizierer, die bestimmte Aspekte
ihres Verhaltens steuern. Der Zugriff auf eine Methode wird beispielsweise mithilfe der Modifizierer public ,
protected , internal und private kontrolliert. C# generalisiert diese Funktionalität, indem benutzerdefinierte
Typen deklarativer Informationen an eine Programmentität angefügt und zur Laufzeit abgerufen werden
können. Programme geben diese deklarativen Informationen durch Definieren und Verwenden von Attributen
an.
Im folgenden Beispiel wird ein HelpAttribute -Attribut deklariert, dass in Programmentitäten platziert werden
kann, um Links zur zugehörigen Dokumentation bereitzustellen.
Alle Attributklassen werden von der Basisklasse Attribute abgeleitet, die von der .NET-Bibliothek bereitgestellt
wird. Attribute können durch Angabe ihres Namens angewendet werden, zusammen mit beliebigen
Argumenten. Diese müssen in eckigen Klammern genau vor der zugehörigen Deklaration eingefügt werden.
Wenn ein Attributname auf Attribute endet, kann dieser Teil des Namens beim Verweis auf das Attribut
weggelassen werden. Beispielsweise kann HelpAttribute wie folgt verwendet werden.
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features",
Topic = "Display")]
public void Display(string text) { }
}
In diesem Beispiel wird HelpAttribute an die Widget -Klasse angefügt. Darüber hinaus wird der Display -
Methode in der Klasse ein weiteres HelpAttribute hinzugefügt. Die öffentlichen Konstruktoren einer
Attributklasse steuern die Informationen, die beim Anfügen des Attributs an eine Programmentität angegeben
werden müssen. Zusätzliche Informationen können angegeben werden, indem auf öffentliche Eigenschaften mit
Lese-/Schreibzugriff der Attributklasse verwiesen wird (z.B. wie der obige Verweis auf die Topic -Eigenschaft).
Die durch Attribute definierten Metadaten können zur Laufzeit mittels Reflektion gelesen und bearbeitet werden.
Wenn mithilfe dieser Technik ein bestimmtes Attribut angefordert wird, wird der Konstruktor für die
Attributklasse mit den in der Programmquelle angegebenen Informationen aufgerufen. Die resultierende
Attributinstanz wird zurückgegeben. Wenn zusätzliche Informationen über Eigenschaften bereitgestellt wurden,
werden diese Eigenschaften auf die vorgegebenen Werte festgelegt, bevor die Attributinstanz zurückgegeben
wird.
Das folgende Codebeispiel zeigt, wie die der Widget -Klasse zugeordneten HelpAttribute -Instanzen und die
dazugehörige Display -Methode abgerufen wird.
Type widgetType = typeof(Widget);
if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}
if (displayMethodAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}
ZU RÜ CK
Einführung in C#
04.11.2021 • 2 minutes to read
Willkommen bei der Einführung in die C#-Tutorials. Die Lektionen beginnen mit interaktivem Code, den Sie in
Ihrem Browser ausführen können. Bevor Sie diese interaktiven Lektionen starten, können Sie die Grundlagen
von C# in der C# 101-Videoreihe erlernen.
In der ersten Lektion werden C#-Konzepte anhand kleiner Codeausschnitte erläutert. Sie lernen die Grundlagen
der C#-Syntax kennen und wie Sie mit Datentypen wie Zeichenfolgen, Zahlen und booleschen Werten arbeiten.
Komplett interaktiv, und Ihr Code ist innerhalb weniger Minuten geschrieben und zur Ausführung bereit. Diese
erste Lektionen setzen keine Vorkenntnisse in der Programmierung oder C#-Sprache voraus.
Sie können diese Tutorials in verschiedenen Umgebungen ausprobieren. Die erlernten Konzepte bleiben gleich.
Es gibt jedoch Unterschiede bei den Umgebungen:
Im Browser auf der Microsoft Dokumentation-Plattform: Hier wird ein ausführbares C#-Codefenster in die
Dokumentationsseiten eingebunden. Sie schreiben C# Code im Browser und führen ihn dort aus.
Auf Microsoft Learn: Dieser Lernpfad enthält mehrere Module, die die Grundlagen von C# vermitteln.
In Jupyter auf Binder: Sie können mit C#-Code in einem Jupyter-Notebook auf Binder experimentieren.
Auf einem lokalen Computer: Sie können das .NET Core SDK herunterladen und die Programme auf Ihrem
Computer entwickeln.
Alle einführenden Tutorials, die auf die „Hallo Welt“-Lektion folgen, sind über einen Onlinebrowser oder in Ihrer
eigenen lokalen Entwicklungsumgebung verfügbar. Am Ende jedes Tutorials entscheiden Sie, ob Sie mit der
nächsten Lektion online oder auf Ihrem eigenen Computer fortfahren möchten. Es sind Links verfügbar, die
Ihnen helfen, Ihre Umgebung einzurichten und mit dem nächsten Tutorial auf Ihrem Computer fortzufahren.
Hallo Welt
Im Hallo Welt-Tutorial erstellen Sie das einfachste C#-Programm. Sie untersuchen den string -Typ und lernen,
mit Text zu arbeiten. Sie können auch den Pfad auf Microsoft Learn oder Jupyter auf Binder verwenden.
Zahlen in C#
Im Tutorial Zahlen in C# erfahren Sie, wie Computer Zahlen speichern und wie Sie Berechnungen mit
verschiedenen Zahlentypen ausführen. Sie lernen die Grundlagen der Rundung und das Ausführen
mathematischer Berechnungen mithilfe von C#. Dieses Tutorial ist auch für die lokale Ausführung auf Ihrem
Computer verfügbar.
Für dieses Tutorial wird vorausgesetzt, dass Sie die Lektion Hallo Welt abgeschlossen haben.
101 LINQ-Beispiele
Für dieses Beispiel ist das globale dotnet-try-Tool erforderlich. Sobald Sie das Tool installiert und das Repository
try-samples geklont haben, können Sie LINQ (Language Integrated Query) mithilfe von 101 Beispielen lernen,
die Sie interaktiv ausführen können. Sie können unterschiedliche Möglichkeiten zum Abfragen, Untersuchen
und Transformieren von Datensequenzen untersuchen.
Einrichten Ihrer lokalen Umgebung
04.11.2021 • 2 minutes to read
Der erste Schritt beim Ausführen eines Tutorials auf Ihrem Computer besteht darin, eine
Entwicklungsumgebung einzurichten. Wählen Sie eine der folgenden Alternativen:
Wie Sie die .NET CLI und den Text- oder Code-Editor Ihrer Wahl verwenden, erfahren Sie im .NET-Tutorial
Hallo Welt in 10 Minuten. Im Tutorial finden Sie eine Anleitung zum Einrichten Ihrer Entwicklungsumgebung
unter Windows, Linux oder macOS.
Um die .NET CLI und Visual Studio Code zu verwenden, installieren Sie das .NET SDK und Visual Studio Code.
Informationen zur Verwendung von Visual Studio 2019 finden Sie unter Tutorial: Erstellen einer einfachen
C#-Konsolen-App in Visual Studio.
Grundlegender Anwendungsentwicklungsworkflow
Bei den Anweisungen in diesen Tutorials wird davon ausgegangen, dass Sie die .NET CLI zum Entwerfen,
Erstellen und Ausführen von Anwendungen verwenden. Sie verwenden die folgenden Befehle:
dotnet new erstellt eine Anwendung. Mit diesem Befehl werden die für Ihre Anwendung erforderlichen
Dateien und Objekte generiert. Bei der Einführung in C#-Tutorials wird immer der Anwendungstyp console
verwendet. Wenn Sie mit den Grundlagen vertraut sind, können Sie mit den Anwendungstypen fortfahren.
dotnet build erstellt die ausführbare Datei.
dotnet run führt die ausführbare Datei aus.
Wenn Sie Visual Studio 2019 für diese Tutorials verwenden, wählen Sie eine Visual Studio-Menüauswahl aus,
wenn Sie von einem Tutorial aufgefordert werden, einen der folgenden CLI-Befehle auszuführen:
Datei > Neu > Projekt erstellt eine Anwendung.
Die Console Application -Projektvorlage wird empfohlen.
Sie erhalten die Möglichkeit, ein Zielframework anzugeben. Die folgenden Tutorials funktionieren am
besten für das Ziel .NET 5 oder höher.
Erstellen > Lösung erstellen erstellt die ausführbare Datei.
Debuggen > Ohne Debuggen star ten führt die ausführbare Datei aus.
Zahlen in C#
Im Tutorial Zahlen in C# erfahren Sie, wie Computer Zahlen speichern und wie Sie Berechnungen mit
verschiedenen Zahlentypen ausführen. Sie lernen die Grundlagen der Rundung und das Ausführen
mathematischer Berechnungen mithilfe von C#.
Für dieses Tutorial wird vorausgesetzt, dass Sie die Hallo Welt-Lektion abgeschlossen haben.
Listensammlung
Die Lektion Listensammlung bietet Ihnen einen Überblick über den Listensammlungstyp, in dem
Datensequenzen speichert werden. Sie erfahren, wie Sie Elemente hinzufügen und entfernen, nach Elementen
suchen und die Listen sortieren. Sie werden verschiedene Arten von Listen erforschen.
Für dieses Tutorial wird vorausgesetzt, dass Sie die oben aufgeführten Lektionen abgeschlossen haben.
Bearbeiten von Ganzzahlen und Gleitkommazahlen
in C#
04.11.2021 • 10 minutes to read
Dieses interaktive Tutorial erläutert die numerischen Typen in C#. Sie schreiben einen Teil des Codes,
kompilieren diesen und führen ihn aus. Das Tutorial enthält eine Reihe von Lektionen, in denen Zahlen und
mathematische Vorgänge in C# untersucht werden. In diesen Lektionen lernen Sie die Grundlagen der
Programmiersprache C# kennen.
Voraussetzungen
Für dieses Tutorial wird vorausgesetzt, dass Sie einen Computer für die lokale Entwicklung eingerichtet haben.
Unter Windows, Linux oder macOS können Sie die .NET CLI zum Programmieren, Erstellen und Ausführen von
Anwendungen verwenden. Auf dem Mac oder unter Windows können Sie Visual Studio 2019 verwenden.
Setupanweisungen finden Sie unter Einführung in .NET-Entwicklungstools.
IMPORTANT
Die C#-Vorlagen für .NET 6 verwenden Anweisungen der obersten Ebene. Ihre Anwendung passt möglicherweise nicht
zum Code in diesem Artikel, wenn Sie bereits ein Upgrade auf die .NET 6-Vorschauversionen durchgeführt haben. Weitere
Informationen finden Sie im Artikel Neue C#-Vorlagen generieren Anweisungen auf oberster Ebene.
Das .NET 6 SDK fügt auch eine Reihe impliziter global using -Anweisungen für Projekte hinzu, die die folgenden SDKs
verwenden:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Diese impliziten global using -Anweisungen enthalten die gängigsten Namespaces für den Projekttyp.
Öffnen Sie Program.cs in Ihrem bevorzugten Editor, und ersetzen Sie den Inhalt der Datei durch folgenden Code:
using System;
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
Führen Sie diesen Code aus, indem Sie dotnet run in Ihr Befehlsfenster eingeben.
Sie haben eine der grundlegenden arithmetischen Operationen mit ganzen Zahlen kennengelernt. Der int -Typ
steht für integer , d. h. eine Null oder eine positive oder negative ganze Zahl. Sie verwenden zum Addieren das
+ -Symbol. Zu den anderen häufig verwendeten arithmetischen Operationen für ganze Zahlen zählen Folgende:
- zur Subtraktion
* zur Multiplikation
/ zur Division
Erkunden Sie zunächst die anderen Operationen. Fügen Sie diese Zeilen nach der Zeile hinzu, die den Wert von
c schreibt:
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
Führen Sie diesen Code aus, indem Sie dotnet run in Ihr Befehlsfenster eingeben.
Wenn Sie möchten, können Sie auch experimentieren, indem Sie mehrere arithmetische Operationen in dieselbe
Zeile schreiben. Testen Sie zum Beispiel c = a + b - 12 * 17; . Das Kombinieren von Variablen und konstanten
Zahlen ist erlaubt.
TIP
Bei Ihren ersten Schritten mit C# (oder einer anderen Programmiersprache) kann es zu Fehlern kommen, wenn Sie Codes
schreiben. Der Compiler findet diese Fehler und meldet diese. Sollten Fehlermeldungen vorliegen, sehen Sie sich den
Beispielcode und den Code in Ihrem Fenster an, um festzustellen, was korrigiert werden muss. Durch diese Übung lernen
Sie die Struktur eines C#-Codes kennen.
Sie haben den ersten Schritt abgeschlossen. Bevor Sie mit dem nächsten Abschnitt beginnen, verschieben wir
den aktuellen Code in eine separate Methode. Eine Methode ist eine Reihe von Anweisungen, die gruppiert
werden und einen Namen erhalten. Sie rufen eine Methode auf, indem Sie den Namen der Methode gefolgt von
() schreiben. Wenn Sie Ihren Code in Methoden organisieren, ist der Einstieg in die Arbeit mit einem neuen
Beispiel einfacher. Anschließend sollte der Code wie folgt aussehen:
using System;
WorkWithIntegers();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
Die Zeile WorkWithIntegers(); ruft die Methode auf. Im folgenden Code wird die Methode deklariert und
definiert.
//WorkWithIntegers();
Mit // wird ein Kommentar in C# begonnen. Kommentare sind Texte, die Sie in Ihrem Quellcode beibehalten,
jedoch nicht als Code ausführen möchten. Der Compiler generiert keinen ausführbaren Code über die
Kommentare. Da WorkWithIntegers() eine Methode ist, müssen Sie nur eine Zeile auskommentieren.
Die Programmiersprache C# definiert anhand von Regeln, die Sie aus der Mathematik kennen, die Rangfolge
verschiedener arithmetischer Operationen. Multiplikation und Division haben gegenüber der Addition und
Subtraktion Vorrang. Überprüfen Sie dies, indem Sie nach dem Aufruf von WorkWithIntegers() den folgenden
Code hinzufügen und dotnet run ausführen:
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
Die Ausgabe zeigt, dass vor der Addition die Multiplikation ausgeführt wurde.
Sie können eine andere Operationsreihenfolge erzwingen, indem Sie die Operation bzw. die Operationen, die
zuerst ausgeführt werden soll bzw. sollen, mit Klammern umschließen. Fügen Sie die folgenden Zeilen hinzu,
und führen Sie sie aus:
d = (a + b) * c;
Console.WriteLine(d);
Machen Sie sich damit vertraut, indem Sie viele verschiedene Operationen kombinieren. Fügen Sie in etwa die
folgenden Zeilen hinzu. Testen Sie erneut dotnet run .
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
Vielleicht haben Sie bereits ein interessantes Verhalten bei den ganzen Zahlen bemerkt. Bei der Division ganzer
Zahlen kommt immer ein ganzzahliges Ergebnis heraus, selbst wenn Sie als Ergebnis einen Dezimal- oder
Bruchteil erwarten würden.
Wenn Sie dieses Verhalten noch nicht beobachtet haben, testen Sie den folgenden Code:
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
// WorkWithIntegers();
OrderPrecedence();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
void OrderPrecedence()
{
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
d = (a + b) * c;
Console.WriteLine(d);
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
}
int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");
Der integer-C#-Typ unterscheidet sich noch in einem weiteren Punkt von einer mathematischen ganzen Zahl:
Der int -Typ ist mit minimalen und maximalen Grenzwerten versehen. Fügen Sie diesen Code hinzu, um die
jeweiligen Grenzwerte zu sehen:
Wenn bei einer Berechnung ein Wert herauskommt, der diese Grenzwerte überschreitet, liegt eine Unterlauf-
oder Überlaufbedingung vor. Die Antwort gibt dann den Bereich der Grenzwerte an. Fügen Sie die folgenden
zwei Zeilen hinzu, um ein Beispiel anzuzeigen:
Beachten Sie, dass die Antwort sehr nah an der minimalen (negativen) ganzen Zahl liegt. Sie entspricht min + 2 .
Die Additionsoperation hat die zulässigen Werte für ganze Zahlen überlaufen . Die Antwort enthält eine sehr
große negative Zahl, da ein Überlauf den größtmöglichen ganzzahligen Wert bis zum kleinstmöglichen Wert
umschließt.
Wenn der int -Typ nicht Ihren Anforderungen entspricht, so gibt es verschiedene numerische Typen mit
anderen Grenzwerten und Genauigkeitsgraden, die Sie verwenden können. Werfen wir im Folgenden einmal
einen Blick auf diese anderen Typen. Bevor Sie mit dem nächsten Abschnitt beginnen, verschieben Sie den in
diesem Abschnitt geschriebenen Code in eine separate Methode. Nennen Sie es TestLimits .
double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Beachten Sie, dass die Antwort die Dezimalzahl des Quotienten enthält. Testen Sie einen etwas komplizierteren
Ausdruck mit Werten vom Typ „double“:
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);
Der Bereich eines Werts vom Typ „double“ ist weitaus größer als bei ganzzahligen Werten. Fügen Sie den
folgenden Code unter dem Code hinzu, den Sie bereits geschrieben haben:
double max = double.MaxValue;
double min = double.MinValue;
Console.WriteLine($"The range of double is {min} to {max}");
Diese Werte werden in der wissenschaftlichen Schreibweise ausgegeben. Die Zahl links von E ist die Mantisse.
Die Zahl rechts ist der Exponent als Potenz von 10. Wie bei Dezimalzahlen in der Mathematik können double-
Werte in C# Rundungsfehler aufweisen. Testen Sie den folgenden Code:
Denken Sie daran, dass 0.3 Periode nicht exakt 1/3 entspricht.
Übung
Testen Sie für den double -Typ andere Berechnungen mit großen und kleinen Zahlen sowie mit Multiplikation
und Division. Testen Sie kompliziertere Berechnungen. Nachdem Sie sich nun einige Zeit lang mit der Übung
auseinander gesetzt haben, kopieren Sie den von Ihnen geschriebenen Code, und fügen Sie ihn in eine neue
Methode ein. Vergeben Sie einen Namen für die neue Methode WorkWithDoubles .
Beachten Sie, dass der Bereich kleiner ist als beim double -Typ. Sie können sehen, dass die Genauigkeit beim Typ
„decimal“ höher ist, wenn Sie den folgenden Code testen:
double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);
decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);
Mit dem Suffix M neben einer Zahl geben Sie an, dass eine Konstante den decimal -Typ verwenden soll.
Andernfalls nimmt der Compiler den Typ double an.
NOTE
Der Buchstabe M wurde als visuell eindeutigster Buchstabe zur Unterscheidung zwischen den Schlüsselwörtern double
und decimal ausgewählt.
Beachten Sie, dass der aus dieser arithmetischen Operation resultierende Wert vom Typ „decimal“ rechts neben
dem Dezimalpunkt mehr Ziffern enthält.
Übung
Nachdem Sie nun die verschiedenen numerischen Typen kennengelernt haben, schreiben Sie Code, der den
Flächeninhalt eines Kreises mit einem Radius von 2,5 cm berechnet. Denken Sie daran, dass der Flächeninhalt
eines Kreises durch das Quadrat des Radius multipliziert mit Pi gebildet wird. Hinweis: .NET bietet eine
Konstante für Pi (Math.PI), die Sie für die Berechnung dieses Werts verwenden können. Math.PI, wie alle im
System.Math -Namespace deklarierten Konstanten, ist ein double -Wert. Aus diesem Grund sollten Sie double
anstelle von decimal -Werten für diese Aufgabe verwenden.
Sie sollten eine Antwort zwischen 19 und 20 erhalten. Sie können Ihre Antwort anhand des fertig gestellten
Beispielcodes auf GitHub prüfen.
Wenn Sie möchten, testen Sie andere Formeln.
Sie haben den Schnellstart „Zahlen in C#“ abgeschlossen. Sie können mit dem Schnellstart Branches und
Schleifen in Ihrer eigenen Entwicklungsumgebung fortfahren.
Weitere Informationen zu Zahlen in C# finden Sie auch in folgenden Artikeln:
Integrale numerische Typen
Numerische Gleitkommatypen
Integrierte numerischer Konvertierungen
Bedingungen für Verzweigungs- und
Schleifenanweisungen
04.11.2021 • 10 minutes to read
In diesem Tutorial erfahren Sie, wie Sie Code schreiben, der Variablen untersucht und basierend auf diesen
Variablen den Ausführungspfad ändert. Sie schreiben einen C#-Code und sehen dort die Ergebnisse der
Kompilierung und Ausführung Ihres Codes. Dieses Tutorial enthält eine Reihe von Lektionen, in denen
Verzweigungs- und Schleifenkonstrukte in C# erkundet werden. In diesen Lektionen lernen Sie die Grundlagen
der Programmiersprache C# kennen.
Voraussetzungen
Für dieses Tutorial wird vorausgesetzt, dass Sie einen Computer für die lokale Entwicklung eingerichtet haben.
Unter Windows, Linux oder macOS können Sie die .NET CLI zum Programmieren, Erstellen und Ausführen von
Anwendungen verwenden. Auf dem Mac und unter Windows können Sie Visual Studio 2019 verwenden.
Setupanweisungen finden Sie unter Einführung in .NET-Entwicklungstools.
IMPORTANT
Die C#-Vorlagen für .NET 6 verwenden Anweisungen der obersten Ebene. Ihre Anwendung passt möglicherweise nicht
zum Code in diesem Artikel, wenn Sie bereits ein Upgrade auf die .NET 6-Vorschauversionen durchgeführt haben. Weitere
Informationen finden Sie im Artikel Neue C#-Vorlagen generieren Anweisungen auf oberster Ebene.
Das .NET 6 SDK fügt auch eine Reihe impliziter global using -Anweisungen für Projekte hinzu, die die folgenden SDKs
verwenden:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Diese impliziten global using -Anweisungen enthalten die gängigsten Namespaces für den Projekttyp.
Dieser Befehl erstellt im aktuellen Verzeichnis eine neue .NET-Konsolenanwendung. Öffnen Sie Program.cs in
Ihrem bevorzugten Editor, und ersetzen Sie den Inhalt durch folgenden Code:
using System;
int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");
Testen Sie diesen Code, indem Sie dotnet run in Ihr Konsolenfenster eingeben. Es sollte folgende Meldung in
Ihrer Konsole angezeigt werden: „Die Antwort ist größer als 10.“ Ändern Sie die Deklaration von b so, dass die
Summe kleiner als 10 ist:
int b = 3;
Geben Sie erneut dotnet run ein. Da die Antwort kleiner als 10 ist, wird nichts ausgegeben. Die von Ihnen
getestete Bedingung ist falsch. Es ist kein Code auszuführen, da Sie lediglich eine der möglichen
Verzweigungen für eine if -Anweisung geschrieben haben: die true-Verzweigung.
TIP
Bei Ihren ersten Schritten mit C# (oder einer anderen Programmiersprache) kann es zu Fehlern kommen, wenn Sie Codes
schreiben. Der Compiler findet und meldet die Fehler. Sehen Sie sich die Fehlerausgabe und den Code, der den Fehler
erzeugt hat, genau an. Der Compilerfehler kann Ihnen in der Regel helfen, das Problem zu erkennen.
Das erste Beispiel veranschaulicht die Vorteile von if -Anweisungen und Boolean-Typen. Ein boolean-Typ ist
eine Variable, die einen der folgenden zwei Werte enthalten kann: true oder false . In C# ist ein besonderer
Typ für boolesche Variablen, bool , definiert. Die if -Anweisung überprüft den Wert eines bool -Typs. Wenn
der Wert true lautet, wird die nach if folgende Anweisung ausgeführt. Andernfalls wird sie übersprungen.
Dieser Vorgang zum Überprüfen von Bedingungen und Ausführen von Anweisungen basierend auf diesen
Bedingungen ist wirkungsvoll.
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
Die Anweisung, die nach dem Schlüsselwort else folgt, wird nur ausgeführt, wenn die zu testende Bedingung
false lautet. Wenn Sie if und else mit booleschen Bedingungen kombinieren, müssen Sie sowohl eine
true - als auch eine false -Bedingung verarbeiten.
IMPORTANT
Der Einzug unter den if - und else -Anweisungen dient zur besseren Lesbarkeit. In der Programmiersprache C#
werden Einzüge oder Leerräume nicht berücksichtigt. Die Anweisung nach dem Schlüsselwort if bzw. else wird
basierend auf der Bedingung ausgeführt. Alle Beispiele in diesem Tutorial folgen der gängigen Vorgehensweise, Zeilen
basierend auf der Ablaufsteuerung von Anweisungen mit einem Einzug zu versehen.
Da Einzüge nicht relevant sind, müssen Sie mit { und } angeben, dass Sie mehr als eine Anweisung im
Rahmen des bedingt ausgeführten Blocks verwenden möchten. C#-Programmierer verwenden solche
geschweifte Klammern in der Regel bei allen if - und else -Anweisungen. Das folgende Beispiel ist identisch
mit dem Inhalt, den Sie erstellt haben. Ändern Sie den obigen Code dahingehend, dass er mit dem folgenden
Code übereinstimmt:
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
TIP
Im restlichen Tutorial enthalten alle Codebeispiele geschweifte Klammern gemäß den allgemein gültigen Vorgehensweisen.
Sie können kompliziertere Bedingungen testen. Fügen Sie den folgenden Code unter dem Code hinzu, den Sie
bereits geschrieben haben:
int c = 4;
if ((a + b + c > 10) && (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
}
Die == -Symboltests für Gleichheit. Die Verwendung von == unterscheidet den Test auf Gleichheit von der
Zuweisung, die Sie in a = 5 gesehen haben.
Das Zeichen && steht für „and“. Es bedeutet, dass beide Bedingungen „true“ lauten müssen, damit die
Anweisung in der true-Verzweigung ausgeführt wird. Diese Beispiele zeigen außerdem, dass Sie in jeder
bedingten Verzweigung mehrere Anweisungen verwenden können, sofern Sie sie mit { und } umschließen.
Sie können auch || für „or “ verwenden. Fügen Sie den folgenden Code unter dem Code hinzu, den Sie bereits
geschrieben haben:
Ändern Sie die Werte von a , b und c , und wechseln Sie zwischen && und || , um sie zu untersuchen. So
werden Sie besser verstehen, wie die Operatoren && und || funktionieren.
Sie haben den ersten Schritt abgeschlossen. Bevor Sie mit dem nächsten Abschnitt beginnen, verschieben wir
den aktuellen Code in eine separate Methode. Dies erleichtert das Arbeiten mit einem neuen Beispiel. Fügen Sie
den vorhandenen Code in eine Methode namens ExploreIf() ein. Rufen Sie sie am Anfang Ihres Programms
auf. Nach diesen Änderungen sollte der Code wie folgt aussehen:
using System;
ExploreIf();
void ExploreIf()
{
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
int c = 4;
if ((a + b + c > 10) && (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the second");
}
Kommentieren Sie den Aufruf von ExploreIf() aus. Dadurch wird die Ausgabe weniger überladen, wenn Sie in
diesem Abschnitt arbeiten:
//ExploreIf();
Mit // wird ein Kommentar in C# begonnen. Kommentare sind Texte, die Sie in Ihrem Quellcode beibehalten,
jedoch nicht als Code ausführen möchten. Der Compiler generiert keinen ausführbaren Code über die
Kommentare.
Die while -Anweisung prüft eine Bedingung und führt die Anweisung oder der Anweisungsblock nach while
aus. Es wiederholt die Überprüfung der Bedingung und die Ausführung dieser Anweisungen, bis die Bedingung
„false“ lautet.
In diesem Beispiel kommt ein weiterer neuer Operator vor. Das ++ -Zeichen nach der counter -Variable ist der
increment -Operator. Er erhöht den Wert von counter um 1 und speichert diesen Wert in der Variable
counter .
IMPORTANT
Stellen Sie sicher, dass die Schleifenbedingung while zu „false“ wechselt, nachdem Sie den Code ausgeführt haben.
Erstellen Sie anderenfalls eine Endlosschleife , durch die das Programm niemals beendet wird. Das wird in diesem Beispiel
nicht gezeigt, weil Sie die programmseitige Verwendung von STRG+C oder anderen Mitteln unterbinden müssen.
Die while -Schleife testet die Bedingung, bevor der Code nach while ausgeführt wird. Die do ... while -
Schleife führt den Code zuerst aus und überprüft anschließend die Bedingung. Die do while-Schleife wird im
folgenden Code gezeigt:
int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);
Diese do -Schleife und die vorherige while -Schleife erzeugen die gleiche Ausgabe.
Der vorherige Code funktioniert auf dieselbe Weise wie die while -Schleife und die do -Schleife, die Sie bereits
verwendet haben. Die for -Anweisung besteht aus drei Teilen, die steuern, wie sie ausgeführt wird.
Der erste Teil ist der for-Initialisierer : int index = 0; deklariert, dass index die Schleifenvariable ist, und legt
den Anfangswert auf 0 fest.
Der mittlere Teil ist die for-Bedingung : index < 10 deklariert, dass diese for -Schleife ausgeführt wird,
solange der Wert des Zählers kleiner als 10 ist.
Der letzte Teil ist der for-Iterator : index++ gibt an, wie die Schleifenvariable geändert wird, nachdem der Block
nach der for -Anweisung ausgeführt wurde. Hier gibt dieser an, dass index bei jeder Blockausführung um 1
erhöht werden soll.
Experimentieren Sie selbst. Testen Sie jede der folgenden Variationen:
Ändern Sie den Initialisierer, um mit einem anderen Wert zu beginnen.
Ändern Sie die Bedingung, um an einem anderen Wert anzuhalten.
Wenn Sie fertig sind, fahren Sie damit fort, mithilfe der erworbenen Kenntnisse selbst Codes zu schreiben.
Eine andere Schleifenanweisung wird in diesem Tutorial nicht behandelt: die foreach -Anweisung. Mit der
foreach -Anweisung wird die Anweisung für jedes Element in einer Sequenz von Elementen ausgeführt. Da sie
am häufigsten mit Sammlungen verwendet wird, wird sie im nächsten Tutorial behandelt.
Sie können eine Schleife innerhalb der anderen schachteln, um Paare zu bilden:
Sie können erkennen, dass sich die äußere Schleife für jede vollständige Ausführung der inneren Schleife einmal
erhöht. Kehren Sie die Schachtelung der Zeilen und Spalten um, und erkennen Sie selbst, was sich ändert. Wenn
Sie fertig sind, fügen Sie den Code aus diesem Abschnitt in eine Methode namens ExploreLoops() ein.
Probieren Sie es selbst aus. Prüfen Sie dann, wie Sie abgeschnitten haben. Sie sollten 63 als Antwort erhalten.
Sie können eine mögliche Antwort sehen, indem Sie sich den fertig gestellten Code auf GitHub ansehen.
Sie haben das Tutorial „Verzweigungen und Schleifen“ abgeschlossen.
Sie können mit dem Tutorial Arrays und Auflistungen in Ihrer eigenen Entwicklungsumgebung fortfahren.
Weitere Informationen zu diesen Begriffen finden Sie in diesen Artikeln:
Auswahlanweisungen
Iterationsanweisungen
Informationen zum Verwalten von
Datensammlungen mithilfe des generischen
Listentyps
04.11.2021 • 6 minutes to read
Dieses Tutorial bietet eine Einführung in die Sprache C# und die Grundlagen der List<T>-Klasse.
Voraussetzungen
Für dieses Tutorial wird vorausgesetzt, dass Sie einen Computer für die lokale Entwicklung eingerichtet haben.
Unter Windows, Linux oder macOS können Sie die .NET CLI zum Programmieren, Erstellen und Ausführen von
Anwendungen verwenden. Auf dem Mac und unter Windows können Sie Visual Studio 2019 verwenden.
Setupanweisungen finden Sie unter Einführung in .NET-Entwicklungstools.
IMPORTANT
Die C#-Vorlagen für .NET 6 verwenden Anweisungen der obersten Ebene. Ihre Anwendung passt möglicherweise nicht
zum Code in diesem Artikel, wenn Sie bereits ein Upgrade auf die .NET 6-Vorschauversionen durchgeführt haben. Weitere
Informationen finden Sie im Artikel Neue C#-Vorlagen generieren Anweisungen auf oberster Ebene.
Das .NET 6 SDK fügt auch eine Reihe impliziter global using -Anweisungen für Projekte hinzu, die die folgenden SDKs
verwenden:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Diese impliziten global using -Anweisungen enthalten die gängigsten Namespaces für den Projekttyp.
Öffnen Sie Program.cs in Ihrem bevorzugten Editor, und ersetzen Sie den vorhandenen Code durch Folgendes:
using System;
using System.Collections.Generic;
Ersetzen Sie <name> durch Ihren eigenen Namen. Speichern Sie Program.cs. Geben Sie dotnet run in Ihrem
Konsolenfenster ein, um es zu testen.
Sie haben eine Liste mit Zeichenfolgen erstellt, dieser Liste drei Namen hinzugefügt und die Namen in
Großbuchstaben ausgegeben. Sie verwenden Konzepte, die Sie in früheren Tutorials kennengelernt haben, um
die Liste zu durchlaufen.
Im Code zum Anzeigen von Namen wird das Feature Zeichenfolgeninterpolation genutzt. Wenn Sie einem
string ein $ -Zeichen voranstellen, können Sie C#-Code in die Zeichenfolgendeklaration einbetten. Der Wert,
den dieser C#-Code generiert, ist eine Zeichenfolge, durch die der C#-Code ersetzt wird. In diesem Beispiel wird
{name.ToUpper()} mit dem jeweiligen in Großbuchstaben konvertierten Namen ersetzt, da Sie die ToUpper-
Methode aufgerufen haben.
Setzen wir nun unsere Forschungen fort.
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Sie haben am Ende der Liste zwei weitere Namen hinzugefügt. Sie haben auch einen entfernt. Speichern Sie die
Datei, und geben Sie dotnet run zum Testen ein.
List<T> ermöglicht Ihnen auch, mithilfe des Indexes auf einzelne Elemente zu verweisen. Platzieren Sie den
Index hinter dem Listennamen zwischen den Zeichen [ und ] . C# verwendet 0 für den ersten Index. Fügen
Sie diesen Code direkt unterhalb des Codes hinzu, den Sie gerade hinzugefügt haben, und probieren Sie es aus:
Sie können nicht auf einen Index zugreifen, der hinter dem Ende der Liste liegt. Denken Sie daran, dass die
Indizes mit 0 (null) beginnen, sodass der größte gültige Index um eins kleiner ist als die Anzahl der Elemente in
der Liste. Sie können mit der Count-Eigenschaft überprüfen, wie lang die Liste ist. Fügen Sie am Ende Ihres
Programms den folgenden Code hinzu:
Speichern Sie die Datei, und geben Sie dotnet run erneut ein, um die Ergebnisse anzuzeigen.
Die Elemente in der Liste können auch sortiert werden. Die Sort-Methode sortiert alle Elemente in der Liste in
der normalen Reihenfolge (Zeichenfolgen alphabetisch). Fügen Sie am Ende Ihres Programms den folgenden
Code hinzu:
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Speichern Sie die Datei, und geben Sie dotnet run ein, um diese neueste Version zu testen.
Bevor Sie mit dem nächsten Abschnitt beginnen, verschieben wir den aktuellen Code in eine separate Methode.
Dies erleichtert das Arbeiten mit einem neuen Beispiel. Fügen Sie den gesamten Code, den Sie geschrieben
haben, in eine neue Methode namens WorkWithStrings() ein. Rufen Sie diese Methode am Anfang Ihres
Programms auf. Anschließend sollte der Code wie folgt aussehen:
using System;
using System.Collections.Generic;
WorkWithString();
void WorkWithString()
{
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
fibonacciNumbers.Add(previous + previous2);
Speichern Sie die Datei, und geben Sie dotnet run ein, um die Ergebnisse anzuzeigen.
TIP
Um sich genau auf diesen Abschnitt zu konzentrieren, können Sie den Code auskommentieren, der
WorkingWithStrings(); aufruft. Setzen Sie einfach zwei / -Zeichen wie folgt vor den Aufruf:
// WorkingWithStrings(); .
Herausforderung
Versuchen Sie, einige dieser Konzepte aus dieser Lektion und früheren Lektionen in einen Zusammenhang zu
bringen. Erweitern Sie das, was Sie bisher bezüglich Fibonacci-Zahlen erstellt haben. Schreiben Sie den Code
zum Generieren der ersten 20 Zahlen der Sequenz. (Hinweis: Die 20. Fibonacci-Zahl lautet 6765.)
Übung abgeschlossen
Eine Beispiellösung finden Sie in Form eines fertiggestellten Beispielcodes auf GitHub.
Mit jeder Iteration der Schleife werden die letzten beiden Ganzzahlen in der Liste summiert, und dieser Wert
wird der Liste hinzugefügt. Die Schleife wird wiederholt, bis der Liste 20 Elemente hinzugefügt worden sind.
Herzlichen Glückwunsch, Sie haben das Listentutorial abgeschlossen. Sie können mit zusätzlichen Tutorials in
Ihrer eigenen Entwicklungsumgebung fortfahren.
Weitere Informationen zum Arbeiten mit dem List -Typ finden Sie im Artikel „.NET-Grundlagen“ unter
Sammlungen. Sie werden auch viele andere Sammlungstypen kennenlernen.
Allgemeine Struktur eines C#-Programms
04.11.2021 • 2 minutes to read
C#-Programme bestehen aus mindestens einer Datei. Jede Datei enthält null oder mehr Namespaces. Ein
Namespace enthält Typen, z. B. Klassen, Strukturen, Schnittstellen, Enumerationen und Delegaten, oder andere
Namespaces. Im folgenden Beispiel wird das Grundgerüst eines C#-Programms dargestellt, das all diese
Elemente enthält.
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
}
Im vorherigen Beispiel werden Anweisungen der obersten Ebene für den Einstiegspunkt des Programms
verwendet. Dieses Feature wurde in C# 9 hinzugefügt. Vor C# 9 war der Einstiegspunkt eine statische Methode
namens Main , wie im folgenden Beispiel gezeigt:
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
class Program
{
static void Main(string[] args)
{
//Your program starts here...
Console.WriteLine("Hello world!");
}
}
}
Verwandte Abschnitte
Informationen zu diesen Programmelementen finden Sie im Abschnitt Typen im Leitfaden zu den Grundlagen:
Klassen
Strukturen
Namespaces
Schnittstellen
Enumerationen
Delegaten
C#-Programmiersprachenspezifikation
Weitere Informationen finden Sie in den grundlegenden Konzepten und derC#-Sprachspezifikation. Die
Sprachspezifikation ist die verbindliche Quelle für die Syntax und Verwendung von C#.
Das C#-Typsystem
04.11.2021 • 13 minutes to read
C# ist eine stark typisierte Sprache. Jede Variable und jede Konstante verfügt über einen Typ, genau wie jeder
Ausdruck, der zu einem Wert ausgewertet wird. Jede Methodendeklaration gibt den Namen, den Typ und die
Art (Wert, Verweis oder Ausgabe) für jeden Eingabeparameter und Rückgabewert an. In der .NET-
Klassenbibliothek sind integrierte numerische Typen und komplexe Typen definiert, die für eine große
Bandbreite an Konstrukten stehen. Dazu gehören das Dateisystem, Netzwerkverbindungen, Sammlungen und
Arrays von Objekten sowie Datumsangaben. In einem typischen C#-Programm werden Typen aus der
Klassenbibliothek und benutzerdefinierte Typen verwendet, die die Konzepte für das Problemfeld des
Programms modellieren.
Die in einem Typ gespeicherten Informationen können die folgenden Elemente umfassen:
Der Speicherplatz, den eine Variable des Typs erfordert
Die maximalen und minimalen Werte, die diese darstellen kann
Die enthaltenen Member (Methoden, Felder, Ereignisse usw.)
Der Basistyp, von dem geerbt wird
Die Schnittstellen, die implementiert werden
Die Arten von zulässigen Vorgängen
Der Compiler verwendet Typinformationen, um sicherzustellen, dass alle im Code ausgeführten Vorgänge
typsicher sind. Wenn Sie z. B. eine Variable vom Typ int deklarieren, können Sie mit dem Compiler die Variable
für Additions- und Subtraktionsvorgänge verwenden. Wenn Sie dieselben Vorgänge für eine Variable vom Typ
bool ausführen möchten, generiert der Compiler einen Fehler, wie im folgenden Beispiel dargestellt:
int a = 5;
int b = a + 2; //OK
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
NOTE
C- und C++-Entwickler sollten beachten, dass in C# bool nicht in int konvertiert werden kann.
Der Compiler bettet die Typinformationen als Metadaten in die ausführbare Datei ein. Die Common Language
Runtime (CLR) verwendet diese Metadaten zur Laufzeit, um die Typsicherheit zu gewährleisten, wenn
Speicherplatz belegt und freigegeben wird.
Nachdem Sie eine Variable deklariert haben, können Sie sie nicht erneut mit einem neuen Typ deklarieren, und
Sie können keinen Wert zuweisen, der nicht mit ihrem deklarierten Typ kompatibel ist. Beispielsweise können
Sie keinen Typ int deklarieren und diesem dann den booleschen Wert true zuweisen. Werte können jedoch
in andere Typen konvertiert werden, etwa wenn diese neuen Variablen zugewiesen oder als
Methodenargumente übergeben werden. Eine Typkonvertierung, die keinen Datenverlust verursacht, wird
automatisch vom Compiler ausgeführt. Eine Konvertierung, die möglicherweise Datenverlust verursacht,
erfordert eine Umwandlung in den Quellcode.
Weitere Informationen finden Sie unter Umwandlung und Typkonvertierungen.
Integrierte Typen
C# stellt einen Standardsatz integrierter Typen bereit. Diese stellen ganze Zahlen, Gleitkommawerte, boolesche
Ausdrücke, Textzeichen, Dezimalwerte und andere Datentypen dar. Es gibt auch integrierte string -Typen und
object -Typen. Diese Typen können Sie in jedem C#-Programm verwenden. Eine vollständige Liste der
integrierten Typen finden Sie unter Integrierte Typen.
Benutzerdefinierte Typen
Sie verwenden die Konstrukte struct , class , interface , enum und record zum Erstellen Ihrer eigenen
benutzerdefinierten Typen. Die .NET-Klassenbibliothek ist eine Sammlung benutzerdefinierter Typen, die Sie in
Ihren eigenen Anwendungen verwenden können. Standardmäßig sind die am häufigsten verwendeten Typen in
der Klassenbibliothek in jedem C#-Programm verfügbar. Andere stehen nur zur Verfügung, wenn Sie
ausdrücklich einen Projektverweis auf die Assembly hinzufügen, in der sie definiert sind. Wenn der Compiler
über einen Verweis auf die Assembly verfügt, können Sie Variablen (und Konstanten) des in dieser Assembly
deklarierten Typs im Quellcode deklarieren. Weitere Informationen finden Sie in der Dokumentation zur .NET-
Klassenbibliothek.
NOTE
Wie Sie sehen, sind die am häufigsten verwendeten Typen alle im System-Namespace organisiert. Jedoch ist es für den
Namespace, in dem ein Typ enthalten ist, unerheblich, ob es sich um einen Werttyp oder einen Referenztyp handelt.
Klassen und Strukturen sind zwei der grundlegenden Konstrukte des allgemeinen Typsystems in .NET. C# 9 fügt
Datensätze hinzu, bei denen es sich um eine Art von Klasse handelt. Bei beiden handelt es sich um eine
Datenstruktur, die einen als logische Einheit zusammengehörenden Satz von Daten und Verhalten kapselt. Die
Daten und Verhalten bilden die Member der Klasse, der Struktur oder des Datensatzes. Die Member beinhalten
ihre Methoden, Eigenschaften, Ereignisse usw. und sind weiter unten in diesem Artikel aufgeführt.
Die Deklaration einer Klasse, Struktur oder eines Datensatzes ist mit einer Blaupause vergleichbar, mit der zur
Laufzeit Instanzen oder Objekte erstellt werden. Wenn Sie eine Klasse, Struktur oder einen Datensatz namens
Person definieren, ist Person der Name des Typs. Wenn Sie eine Variable p vom Typ Person deklarieren und
initialisieren, wird p als Objekt oder Instanz von Person bezeichnet. Vom selben Typ Person können mehrere
Instanzen erstellt werden, und jede Instanz kann über unterschiedliche Werte in ihren Eigenschaften und Feldern
verfügen.
Eine Klasse ist ein Verweistyp. Wenn ein Objekt des Typs erstellt wird, enthält die Variable, der das Objekt
zugewiesen wurde, lediglich einen Verweis auf den entsprechenden Speicherort. Wenn der Objektverweis einer
neuen Variablen zugewiesen wird, verweist die neue Variable auf das ursprüngliche Objekt. Über eine Variable
vorgenommene Änderungen gelten auch für die andere Variable, da beide auf dieselben Daten verweisen.
Eine Struktur ist ein Werttyp. Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen
wird, die eigentlichen Daten der Struktur. Wenn die Struktur einer neuen Variable zugewiesen wird, wird sie
kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien der gleichen
Daten. Änderungen an einer Kopie wirken sich nicht auf die andere Kopie aus.
Datensatztypen können entweder Verweistypen ( record class ) oder Werttypen ( record struct ) sein.
Im Allgemeinen werden Klassen verwendet, um komplexeres Verhalten zu modellieren. Klassen speichern in der
Regel Daten, die geändert werden sollen, nachdem ein Klassenobjekt erstellt wurde. Strukturen eignen sich am
besten für kleine Datenstrukturen. In Strukturen sind in der Regel Daten gespeichert, deren Änderung nach dem
Erstellen der Struktur nicht beabsichtigt ist. Datensatztypen sind Datenstrukturen mit zusätzlichen vom Compiler
synthetisierten Membern. In Datensätzen sind in der Regel Daten gespeichert, deren Änderung nach dem
Erstellen des Objekts nicht beabsichtigt ist.
Werttypen
Werttypen werden von System.ValueType abgeleitet, was wiederum von System.Object abgeleitet wird. Typen,
die von System.ValueType abgeleitet werden, weisen ein besonderes Verhalten in der CLR auf. Werttypvariablen
enthalten ihre Werte direkt. Der Arbeitsspeicher für eine Struktur wird inline in dem Kontext zugeordnet, in dem
die Variable deklariert ist. Für Werttypvariablen erfolgt keine getrennte Heapzuordnung bzw. kein Mehraufwand
für Garbage Collection. Sie können record struct -Typen deklarieren, die Werttypen sind, und die
synthetisierten Member für Datensätze einschließen.
Zwei Kategorien von Werttypen sind verfügbar: struct und enum .
Die integrierten numerischen Typen sind Strukturen und verfügen über Felder und Methoden, auf die Sie
zugreifen können:
Sie deklarieren diese jedoch und weisen ihnen Werte zu, als wären es einfache, nicht aggregierte Typen:
Werttypen sind versiegelt. Sie können keinen Typ aus einem Werttyp ableiten, z. B. System.Int32. Sie können
keine Struktur definieren, die von einer benutzerdefinierten Klasse oder Struktur erben kann, weil eine Struktur
nur von System.ValueType erben kann. Eine Struktur kann jedoch eine oder mehrere Schnittstellen
implementieren. Sie können einen Strukturtyp in jeden beliebigen Schnittstellentyp umwandeln, den er
implementiert. Diese Umwandlung verursacht einen Boxing-Vorgang, mit dem die Struktur von einem
Referenztypobjekt im verwalteten Heap umschlossen wird. Boxing-Vorgänge werden auch ausgeführt, wenn Sie
einen Werttyp an eine Methode übergeben, die System.Object oder einen beliebigen Schnittstellentyp als
Eingabeparameter akzeptiert. Weitere Informationen finden Sie unter Boxing und Unboxing.
Sie können das struct-Schlüsselwort verwenden, um eigene benutzerdefinierte Werttypen zu erstellen. In der
Regel wird eine Struktur als Container für einen kleinen Satz verwandter Variablen verwendet, wie im folgenden
Beispiel dargestellt:
public struct Coords
{
public int x, y;
Weitere Informationen über Strukturen finden Sie unter Struktur-Typen. Weitere Informationen zu Werttypen
finden Sie unter Werttypen.
Die andere Kategorie von Werttypen ist enum . Eine Enumeration definiert einen Satz benannter ganzzahliger
Konstanten. So enthält z.B. die System.IO.FileMode-Enumeration in der .NET-Klassenbibliothek mehrere
benannte ganzzahlige Konstanten, die festlegen, wie eine Datei geöffnet werden soll. Die Definition erfolgt wie
im folgenden Beispiel:
Die System.IO.FileMode.Create-Konstante besitzt den Wert 2. Der Name ist jedoch für Personen, die den
Quellcode lesen, viel aussagekräftiger. Aus diesem Grund ist es besser, anstelle von Konstantenliteralen
Enumerationen zu verwenden. Weitere Informationen finden Sie unter System.IO.FileMode.
Alle Enumerationen erben von System.Enum, was wiederum von System.ValueType erbt. Alle Regeln, die für
Strukturen gelten, gelten auch für Enumerationen. Weitere Informationen zu Enumerationen finden Sie unter
Enumerationstypen.
Verweistypen
Ein Typ, der als class , record , delegate , Array oder interface definiert ist, ist ein reference type .
Wenn Sie eine Variable eines reference type deklarieren, enthält sie den Wert null , bis Sie ihr eine Instanz
dieses Typs zuweisen oder über den new -Operator eine Instanz erstellen. Das folgende Beispiel veranschaulicht
die Erstellung und Zuweisung einer Klasse:
interface kann nicht direkt über den new -Operator instanziiert werden. Erstellen Sie stattdessen eine Instanz
einer Klasse, die die Schnittstelle implementiert, und weisen Sie sie zu. Betrachten Sie das folgenden Beispiel:
MyClass myClass = new MyClass();
Beim Erstellen des Objekts wird der Arbeitsspeicher auf dem verwalteten Heap zugewiesen. Die Variable enthält
nur einen Verweis auf den Speicherort des Objekts. Für Typen im verwalteten Heap ist sowohl bei der
Zuweisung als auch bei der Bereinigung Mehraufwand erforderlich. Garbage Collection ist die automatische
Speicherverwaltungsfunktion der CLR, die die Bereinigung ausführt. Die Garbage Collection ist jedoch auch
stark optimiert. In den meisten Szenarien führt sie nicht zu einem Leistungsproblem. Weitere Informationen zur
Garbage Collection finden Sie unter Automatische Speicherverwaltung.
Alle Arrays sind Referenztypen, selbst wenn ihre Elemente Werttypen sind. Arrays werden implizit von der
System.Array-Klasse abgeleitet. Sie deklarieren und verwenden diese jedoch mit der vereinfachten, von C#
bereitgestellten Syntax, wie im folgenden Beispiel dargestellt:
Referenztypen bieten volle Vererbungsunterstützung. Beim Erstellen einer Klasse können Sie von jeder anderen
Schnittstelle oder Klasse erben, die nicht als versiegelt definiert ist. Andere Klassen können von Ihrer Klasse
erben und Ihre virtuellen Methoden überschreiben. Weitere Informationen zum Erstellen eigener Klassen finden
Sie unter Klassen, Strukturen und Datensätze. Weitere Informationen zur Vererbung und zu virtuellen Methoden
finden Sie unter Vererbung.
Generische Typen
Ein Typ kann mit einem oder mehreren Typparametern deklariert werden, die als Platzhalter für den eigentlichen
Typ verwendet werden (den konkreten Typ). Der konkrete Typ wird beim Erstellen einer Instanz des Typs vom
Clientcode bereitgestellt. Solche Typen werden als generische Typen bezeichnet. Beispielsweise besitzt der .NET-
Typ System.Collections.Generic.List<T> einen Typparameter, der konventionsgemäß den Namen T erhält. Beim
Erstellen einer Instanz des Typs geben Sie den Typ der Objekte an, die die Liste enthalten soll, z. B. string :
Die Verwendung des Typparameters ermöglicht die Wiederverwendung der Klasse für beliebige Elementtypen,
ohne die einzelnen Elemente in object konvertieren zu müssen. Generische Sammlungsklassen werden als stark
typisierte Sammlungen bezeichnet, weil der Compiler den jeweiligen Typ der Elemente in der Sammlung kennt
und zur Kompilierzeit einen Fehler auslösen kann, wenn Sie beispielsweise versuchen, dem stringList -Objekt
im vorherigen Beispiel eine ganze Zahl hinzuzufügen. Weitere Informationen finden Sie unter Generics.
In anderen Fällen ist der Typ zur Kompilierzeit ein anderer, wie in den folgenden beiden Beispielen gezeigt:
In den beiden vorherigen Beispielen ist der Typ zur Laufzeit ein Typ string . Der Typ zur Kompilierzeit ist
object in der ersten Zeile und IEnumerable<char> in der zweiten Zeile.
Wenn sich die beiden Typen für eine Variable unterscheiden, ist es wichtig zu verstehen, wann der Typ zur
Kompilierzeit und wann der Typ zur Laufzeit auftritt. Der Typ zur Kompilierzeit bestimmt alle Aktionen, die vom
Compiler ausgeführt werden. Diese Compileraktionen umfassen die Auflösung von Methodenaufrufen, die
Überladungsauflösung und verfügbare implizite und explizite Umwandlungen. Der Typ zur Laufzeit bestimmt
alle Aktionen, die zur Laufzeit aufgelöst werden. Diese Laufzeitaktionen umfassen das Verteilen virtueller
Methodenaufrufe, das Auswerten von is - und switch -Ausdrücken sowie andere Typtest-APIs. Um besser zu
verstehen, wie der Code mit Typen interagiert, ermitteln Sie, welche Aktion für welchen Typ gilt.
Verwandte Abschnitte
Weitere Informationen finden Sie in den folgenden Artikeln:
Integrierte Typen
Werttypen
Verweistypen
C#-Sprachspezifikation
Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche
Quelle für die Syntax und Verwendung von C#.
Objektorientiertes Programmieren
04.11.2021 • 4 minutes to read
Kapselung
Kapselung wird gelegentlich als erster Pfeiler oder als Prinzip der objektorientierten Programmierung
bezeichnet. Eine Klasse oder Struktur kann festlegen, inwieweit Code außerhalb der Klasse oder Struktur auf
deren Member zugreifen kann. Nicht für die Verwendung von außerhalb der Klasse oder Assembly vorgesehene
Methoden und Variablen können ausgeblendet werden, um die Wahrscheinlichkeit von Programmierfehlern
und böswilligen Angriffen zu verringern. Weitere Informationen finden Sie unter Objektorientierte
Programmierung.
Member
Die Member eines Typs umfassen alle Methoden, Felder, Konstanten, Eigenschaften und Ereignisse. In C# gibt es
im Gegensatz zu einigen anderen Sprachen keine globalen Variablen oder Methoden. Selbst der Einstiegspunkt
eines Programms, die Main -Methode, muss innerhalb einer Klasse oder Struktur deklariert werden (implizit im
Fall von Anweisungen der obersten Ebene).
In der folgenden Liste werden sämtliche Arten von Membern aufgeführt, die in einer Klasse, Struktur oder
einem Datensatz deklariert werden können.
Felder
Konstanten
Eigenschaften
Methoden
Konstruktoren
Ereignisse
Finalizer
Indexer
Operatoren
Geschachtelte Typen
Zugriff
Einige Methoden und Eigenschaften sind für den Aufruf oder Zugriff von als Clientcode bezeichnetem Code
außerhalb einer Klasse oder Struktur vorgesehen. Andere Methoden und Eigenschaften dienen nur der
Verwendung in der Klasse oder Struktur selbst. Es ist wichtig, den Zugriff auf den Code einzuschränken, damit
nur der Clientcode darauf zugreifen kann, der dafür vorgesehen ist. Inwieweit Clientcode auf die Typen und
deren Member zugreifen kann, können Sie mit folgenden Zugriffsmodifizierern festlegen:
public
protected
internal
protected internal
private
private protected.
Die Standardeinstellung für den Zugriff lautet private .
Vererbung
Klassen ( jedoch nicht Strukturen) unterstützen das Konzept der Vererbung. Eine Klasse, die von einer anderen
Klasse, der sogenannten Basisklasse, abgeleitet ist, enthält automatisch alle öffentlichen, geschützten und
internen Member der Basisklasse mit Ausnahme der Konstruktoren und Finalizer. Weitere Informationen finden
Sie unter Vererbung und Polymorphie.
Klassen können als abstrakt deklariert werden. Das bedeutet, dass mindestens eine ihrer Methoden nicht
implementiert ist. Obwohl abstrakte Klassen nicht direkt instanziiert werden können, können Sie als
Basisklassen für andere Klassen dienen, von denen die fehlende Implementierung bereitgestellt wird. Klassen
können auch als versiegelt deklariert werden, um zu verhindern, dass andere Klassen von ihnen erben.
Schnittstellen
Klassen, Strukturen und Datensätze können mehrere Schnittstellen erben. Von einer Schnittstelle erben
bedeutet, dass der Typ alle in der Schnittstelle definierten Methoden implementiert. Weitere Informationen
finden Sie unter Schnittstellen.
Generische Typen
Klassen, Strukturen und Datensätze können mit einem oder mehreren Typparametern definiert werden. Der Typ
wird beim Erstellen einer Instanz des Typs vom Clientcode bereitgestellt. Beispielsweise ist die Klasse List<T> im
Namespace System.Collections.Generic mit einem Typparameter definiert. Vom Clientcode wird eine Instanz von
List<string> oder List<int> erstellt, um den Typ anzugeben, den die Liste enthalten soll. Weitere
Informationen finden Sie unter Generics.
Statische Typen
Klassen (nicht jedoch Strukturen oder Datensätze) können als static deklariert werden. Eine statische Klasse
kann nur statische Member enthalten und nicht mit dem Schlüsselwort new instanziiert werden. Beim Laden
des Programms wird eine Kopie der Klasse in den Speicher geladen. Auf deren Member wird über den
Klassennamen zugegriffen. Klassen, Strukturen und Datensätze können statische Member enthalten.
Geschachtelte Typen
Eine Klasse, Struktur oder ein Datensatz kann innerhalb einer anderen Klasse, Struktur oder eines Datensatzes
geschachtelt werden.
Partielle Typen
Sie können einen Teil einer Klasse, Struktur oder Methode in einer Codedatei und einen anderen Teil in einer
separaten Codedatei definieren.
Objektinitialisierer
Sie können Klassen- oder Strukturobjekte sowie Auflistungen von Objekten instanziieren und initialisieren,
indem Sie ihren Eigenschaften Werte zuweisen.
Anonyme Typen
In Situationen, in denen es unkomfortabel oder nicht erforderlich ist, eine benannte Klasse zu erstellen,
verwenden Sie anonyme Typen. Anonyme Typen werden durch ihre benannten Datenmember definiert.
Erweiterungsmethoden
Sie können eine Klasse „erweitern“, ohne eine abgeleitete Klasse zu erstellen, indem Sie einen separaten Typ
erstellen. Dieser Typ enthält Methoden, die aufgerufen werden können, als ob sie zum ursprünglichen Typ
gehörten.
Datensätze
C# 9 führt den record -Typ ein. Dies ist ein Verweistyp, den Sie anstelle einer Klasse oder einer Struktur
erstellen können. Datensätze sind Klassen mit dem integrierten Verhalten zum Kapseln von Daten in
unveränderlichen Typen. Mit C# 10 wird der Werttyp record struct eingeführt. Ein Datensatz (entweder
record class oder record struct ) bietet die folgenden Features:
C#-Programmiersprachenspezifikation
Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche
Quelle für die Syntax und Verwendung von C#.
Ausnahmen und Ausnahmebehandlung
04.11.2021 • 2 minutes to read
Die C#-Funktionen zur Ausnahmebehandlung helfen Ihnen dabei, unerwartete oder außergewöhnliche
Situationen zu verarbeiten, die beim Ausführen von Programmen auftreten können. Die Ausnahmebehandlung
verwendet die Schlüsselwörter try , catch und finally , um Aktionen zu testen, die möglicherweise nicht
erfolgreich sind, um Fehler zu behandeln, wenn Sie entscheiden, dass dies vernünftig ist, und um später
Ressourcen zu bereinigen. Ausnahmen können von der Common Language Runtime (CLR), von .NET bzw.
anderen Drittanbieterbibliotheken oder vom Anwendungscode generiert werden. Ausnahmen werden mit dem
Schlüsselwort throw erstellt.
In vielen Fällen wird eine Ausnahme nicht von einer Methode ausgelöst, die von Ihrem Code direkt aufgerufen
wurde, sondern von einer anderen Methode weiter unten in der Aufrufliste. Wenn eine Ausnahme ausgelöst
wird, entlädt die CLR die Liste, sucht nach einer Methode mit einem catch -Block für den spezifischen
Ausnahmetyp und führt den ersten gefundenen catch -Block aus. Wenn kein geeigneter catch -Block in der
Aufrufliste gefunden wird, beendet die CLR den Prozess und zeigt eine Meldung für den Benutzer an.
In diesem Beispiel testet eine Methode, ob eine Division durch Null möglich ist, und fängt den Fehler ab. Ohne
Ausnahmebehandlung würde dieses Programm mit dem Fehler DivideByZeroException wurde nicht
behandelt beendet.
try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}
C#-Programmiersprachenspezifikation
Weitere Informationen erhalten Sie unter Ausnahmen in der C#-Sprachspezifikation. Die Sprachspezifikation ist
die verbindliche Quelle für die Syntax und Verwendung von C#.
Siehe auch
SystemException
C#-Schlüsselwörter
throw
try-catch
try-finally
try-catch-finally
Ausnahmen
Neuerungen in C# 10.0
04.11.2021 • 3 minutes to read
IMPORTANT
In diesem Artikel werden die Features erläutert, die ab .NET 6 Vorschau 7 in C# 10.0 verfügbar sind. Zurzeit erfolgt die
Dokumentation der Verbesserungen für C# 10.0. Sie können den Fortschritt der Dokumentation in diesem Projekt
überprüfen.
Mit Version 10.0 wird die Sprache C# um die folgenden Features und Verbesserungen erweitert:
Datensatzstrukturen
Verbesserungen von Strukturtypen
Handler für interpolierte Zeichenfolgen
global using -Direktiven
Dateibezogene Namespacedeklaration
Muster für erweiterte Eigenschaften
Lässt interpolierte Zeichenfolgen vom Typ const zu
Datensatztypen können ToString() versiegeln
Lässt Zuweisung und Deklaration in derselben Dekonstruktion zu
Lässt AsyncMethodBuilder -Attribut für Methoden zu
Einige der Features, die Sie ausprobieren können, sind nur verfügbar, wenn Sie Ihre Sprachversion auf „preview“
festlegen. Diese Features werden möglicherweise in zukünftigen Vorschauversionen noch weiter verfeinert,
bevor .NET 6.0 veröffentlicht wird.
C# 10.0 wird in .NET 6 unterstützt. Weitere Informationen finden Sie unter C#-Sprachversionsverwaltung.
Sie können das neueste .NET 6.0 SDK über die .NET-Downloadseite herunterladen. Sie können auch die Version
Visual Studio 2022 Preview herunterladen, die das .NET 6.0 Preview SDK enthält.
Datensatzstrukturen
Sie können Werttyp-Datensätze deklarieren, indem Sie entweder die record struct - oder
readonly record struct - Deklaration verwenden. Sie können nun mit der Deklaration record class
verdeutlichen, dass record ein Referenztyp ist.
Globale using-Anweisungen
Sie können den global -Modifizierer einer beliebigen using-Anweisung hinzufügen, um den Compiler
anzuweisen, dass die Direktive für alle Quelldateien in der Kompilierung gilt. Dies sind in der Regel alle
Quelldateien in einem Projekt.
Dateibezogene Namespacedeklaration
Sie können eine neue Form der namespace -Deklaration verwenden, um zu deklarieren, dass alle nachfolgenden
Deklarationen Member des deklarierten Namespace sind:
namespace MyNamespace;
Diese neue Syntax spart sowohl horizontal als auch vertikal Platz für die gängigsten namespace -Deklarationen.
{ Prop1.Prop2: pattern }
NOTE
Wenn Sie .NET 6.0 Preview 5 verwenden, muss für dieses Feature das <LangVersion> -Element in Ihrer CSPROJ-Datei auf
preview festgelegt werden.
NOTE
Wenn Sie .NET 6.0 Preview 5 verwenden, muss für dieses Feature das <LangVersion> -Element in Ihrer CSPROJ-Datei auf
preview festgelegt werden.
// Initialization:
(int x, int y) = point;
// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;
int x = 0;
(x, int y) = point;
NOTE
Wenn Sie .NET 6.0 Preview 5 verwenden, muss für dieses Feature das <LangVersion> -Element in Ihrer CSPROJ-Datei auf
preview festgelegt werden.
Mit Version 9.0 wird die Sprache C# um die folgenden Features und Verbesserungen erweitert:
Datensätze
init-only-Setter
Top-Level-Anweisungen
Verbesserungen am Musterabgleich:
Leistung und Interop
Integerwerte mit nativer Größe
Funktionszeiger
Unterdrücken der Ausgabe des Flags „localsinit“
Anpassen und Fertigstellen von Features
Zieltypisierte new -Ausdrücke
Anonyme static -Funktionen
Bedingter Ausdruck mit Zieltyp
Kovariante Rückgabetypen
Unterstützung für die Erweiterung GetEnumerator für foreach -Schleifen
Parameter zum Verwerfen von Lambdafunktion
Attribute in lokalen Funktionen
Unterstützung für Code-Generatoren
Modulinitialisierer
Neue Features für partielle Methoden
C# 9.0 wird in .NET 5 unterstützt. Weitere Informationen finden Sie unter C#-Sprachversionsverwaltung.
Sie können das neueste .NET SDK über die .NET-Downloadseite herunterladen.
Eintragstypen
In C# 9.0 wurden Datensatztypen (Eintragstypen) eingeführt. Sie verwenden das record -Schlüsselwort, um
einen Verweistyp zu definieren, der integrierte Funktionalität zum Kapseln von Daten bereitstellt. Sie können
Datensatztypen mit unveränderlichen Eigenschaften erstellen, indem Sie Positionsparameter oder
Standardeigenschaftensyntax verwenden:
Sie können auch Datensatztypen mit änderbaren Eigenschaften und Feldern erstellen:
public record Person
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
};
Datensätze können zwar änderbar sein, sind jedoch primär dafür vorgesehen, unveränderliche Datenmodelle zu
unterstützen. Der Datensatztyp bietet die folgenden Funktionen:
Präzise Syntax zum Erstellen eines Verweistyps mit unveränderlichen Eigenschaften
Verhalten, das für einen datenzentrierten Verweistyp nützlich ist:
Wertgleichheit
Präzise Syntax für die nicht destruktive Mutation
Integrierte Formatierung für die Anzeige
Unterstützung für Vererbungshierarchien
Sie können Strukturtypen verwenden, um datenzentrierte Typen zu entwerfen, die Wertgleichheit und wenig
oder kein Verhalten bereitstellen. Bei relativ großen Datenmodellen haben Strukturtypen jedoch einige
Nachteile:
Sie unterstützen keine Vererbung.
Sie sind weniger effizient bei der Bestimmung der Wertgleichheit. Bei Werttypen verwendet die
ValueType.Equals-Methode Reflexion, um alle Felder zu suchen. Für Datensätze generiert der Compiler die
Equals -Methode. In der Praxis ist die Implementierung von Wertgleichheit in Datensätzen messbar
schneller.
In einigen Szenarien wird mehr Arbeitsspeicher verwendet, da jede Instanz über eine vollständige Kopie aller
Daten verfügt. Datensatztypen sind Verweistypen, sodass eine Datensatzinstanz nur einen Verweis auf die
Daten enthält.
Positionssyntax für die Eigenschaftendefinition
Sie können Positionsparameter verwenden, um die Eigenschaften eines Datensatzes zu deklarieren und die
Eigenschaftswerte zu initialisieren, wenn Sie eine Instanz erstellen:
Wenn Sie die Positionssyntax für die Eigenschaftendefinition verwenden, erstellt der Compiler Folgendes:
Eine öffentliche, automatisch implementierte Init-only-Eigenschaft für jeden Positionsparameter, der in der
Datensatzdeklaration bereitgestellt wird. Eine Init-only-Eigenschaft kann nur im Konstruktor oder mit einem
Eigenschafteninitialisierer festgelegt werden.
Ein primärer Konstruktor, dessen Parameter mit den Parametern mit fester Breite der Datensatzdeklaration
übereinstimmen
Eine Deconstruct -Methode mit einem out -Parameter für jeden Positionsparameter, der in der
Datensatzdeklaration bereitgestellt wird.
Weitere Informationen finden Sie unter Positionssyntax im C#-Sprachreferenzartikel zu Datensätzen (Records).
Unveränderlichkeit
Ein Datensatztyp ist nicht notwendigerweise unveränderlich. Sie können Eigenschaften mit set -
Zugriffsmethoden und Feldern deklarieren, die nicht readonly sind. Doch obwohl Datensätze änderbar sein
können, vereinfachen sie die Erstellung unveränderlicher Datenmodelle. Eigenschaften, die Sie mit
Positionssyntax erstellen, sind unveränderlich.
Unveränderlichkeit kann nützlich sein, wenn Sie möchten, dass ein datenzentrierter Typ threadsicher ist oder ein
Hashcode in einer Hashtabelle unverändert bleibt. Dadurch können Fehler verhindert werden, die auftreten,
wenn Sie ein Argument als Verweis an eine Methode übergeben und die Methode den Argumentwert
unerwartet ändert.
Die für Datensatztypen eindeutigen Features werden von durch den Compiler synthetisierte Methoden
implementiert, und keine dieser Methoden beeinträchtigt die Unveränderlichkeit durch Ändern des
Objektzustands.
Wertgleichheit
Wertgleichheit bedeutet, dass zwei Variablen eines Datensatztyps gleich sind, wenn die Typen sowie alle
Eigenschafts- und Feldwerte übereinstimmen. Bei anderen Verweistypen bedeutet Gleichheit Identität. Zwei
Variablen eines Verweistyps sind demnach gleich, wenn sie auf dasselbe Objekt verweisen.
Im folgenden Beispiel wird die Wertgleichheit von Datensatztypen veranschaulicht:
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
In class -Typen könnten Sie Gleichheitsmethoden und -operatoren manuell überschreiben, um Wertgleichheit
zu erzielen, aber das Entwickeln und Testen dieses Codes wäre zeitaufwändig und fehleranfällig. Wenn diese
Funktionalität integriert ist, werden Fehler verhindert, die sich daraus ergeben würden, dass vergessen wird,
benutzerdefinierten Überschreibungscode zu aktualisieren, wenn Eigenschaften oder Felder hinzugefügt oder
geändert werden.
Weitere Informationen finden Sie unter Wertgleichheit im C#-Sprachreferenzartikel zu Datensätzen (Records).
Nichtdestruktive Mutation
Wenn Sie unveränderliche Eigenschaften einer Datensatzinstanz mutieren müssen, können Sie einen with -
Ausdruck verwenden, um eine nichtdestruktive Mutation zu erzielen. Ein with -Ausdruck erstellt eine neue
Datensatzinstanz, bei der es sich um eine Kopie einer vorhandenen Datensatzinstanz handelt, bei der bestimmte
Eigenschaften und Felder geändert wurden. Mit der Objektinitialisierersyntax können Sie die zu ändernden
Werte angeben, wie im folgenden Beispiel gezeigt:
public record Person(string FirstName, string LastName)
{
public string[] PhoneNumbers { get; init; }
}
<record type name> { <property name> = <value>, <property name> = <value>, ...}
Für Verweistypen wird der Typname des Objekts, auf das die Eigenschaft verweist, anstelle des
Eigenschaftenwerts angezeigt. Im folgenden Beispiel ist das Array ein Verweistyp, sodass System.String[]
anstelle der tatsächlichen Arrayelementwerte angezeigt wird:
Damit zwei Datensatzvariablen gleich sind, muss der Laufzeittyp gleich sein. Die Typen der enthaltenden
Variablen können unterschiedlich sein. Dies wird im folgenden Codebeispiel veranschaulicht:
Im Beispiel verfügen alle Instanzen über dieselben Eigenschaften und Eigenschaftswerte. student == teacher
gibt jedoch False zurück, obwohl beide Variablen den Typ Person haben. Und student == student2 gibt True
zurück, obwohl eine Instanz eine Person -Variable und eine Instanz eine Student -Variable ist.
Alle öffentlichen Eigenschaften und Felder sowohl von abgeleiteten als auch von Basistypen sind in der
ToString -Ausgabe enthalten, wie im folgenden Beispiel gezeigt:
init-only-Setter
Nur-init-Setter bieten eine konsistente Syntax zum Initialisieren von Objektmembern.
Eigenschafteninitialisierer verdeutlichen, welcher Wert welche Eigenschaft festlegt. Der Nachteil ist, dass diese
Eigenschaften festlegbar sein müssen. Ab C# 9.0 können Sie init -Zugriffsmethoden anstelle von set -
Zugriffsmethoden für Eigenschaften und Indexer erstellen. Aufrufer können diese Werte mithilfe der Syntax von
Eigenschafteninitialisierern in Erstellungsausdrücken festlegen. Diese Eigenschaften sind jedoch nach Abschluss
der Erstellung schreibgeschützt. Nur-init-Setter bieten Ihnen die Möglichkeit, den Zustand innerhalb eines
bestimmten Zeitfensters zu ändern. Dieses Zeitfenster schließt sich nach Abschluss der Konstruktionsphase. Die
Konstruktionsphase endet effektiv, nachdem die gesamte Initialisierung, einschließlich aller
Eigenschafteninitialisierer und with-Ausdrücke, abgeschlossen wurde.
Sie können Nur- init -Setter in einem jedem Typ deklarieren, den Sie schreiben. Die folgende Struktur definiert
z. B. eine Struktur zur Wetterbeobachtung:
Aufrufer können die Werte mithilfe der Syntax von Eigenschafteninitialisierern festlegen und gleichzeitig die
Unveränderlichkeit wahren:
Ein Versuch, eine Beobachtung nach der Initialisierung zu ändern, führt zu einem Compilerfehler:
// Error! CS8852.
now.TemperatureInCelsius = 18;
Nur-init-Setter können nützlich sein, um Basisklasseneigenschaften von abgeleiteten Klassen festzulegen. Sie
können auch mithilfe von Hilfsprogrammen abgeleitete Eigenschaften in einer Basisklasse festlegen. Positionelle
Datensätze deklarieren Eigenschaften mithilfe von Nur-init-Settern. Diese Setter werden in with-Ausdrücken
verwendet. Sie können Nur-init-Setter für jedes class -, struct - oder record -Element deklarieren, das Sie
definieren.
Weitere Informationen finden Sie unter init (C#-Referenz).
Top-Level-Anweisungen
Mithilfe von allgemeinen Anweisungen lässt sich der Code in vielen Anwendungen stark verkürzen. Dies ist
das kanonische Hallo-Welt-Programm („Hello World“):
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Nur eine der Codezeilen ruft eine Aktion hervor. Mit allgemeinen Anweisungen können Sie all diese
Codebausteine durch die using -Anweisung und die eine Zeile ersetzen, die die Aktion verursacht:
using System;
Console.WriteLine("Hello World!");
Wenn Sie ein einzeiliges Programm schreiben möchten, können Sie die using -Anweisung auch entfernen und
den vollqualifizierten Typnamen verwenden:
System.Console.WriteLine("Hello World!");
Allgemeine Anweisungen dürfen nur einer Anwendungsdatei eingesetzt werden. Wenn der Compiler in
mehreren Quelldateien allgemeine Anweisungen findet, führt dies zu einem Fehler. Ein Fehler wird ebenfalls
zurückgegeben, wenn Sie allgemeine Anweisungen mit einer deklarierten Einstiegspunktmethode des
Programms kombinieren (in der Regel eine Main -Methode). Sie können sich dies vorstellen, als ob eine Datei
die Anweisungen enthält, die normalerweise in die Main -Methode einer Program -Klasse geschrieben werden.
Einer der häufigsten Anwendungsfälle für dieses Feature ist die Erstellung von Lehrmaterial. Angehende C#-
Entwickler können die kanonische „Hallo Welt“-Anwendung in einer oder zwei Codezeilen schreiben. Keiner der
zusätzlichen Codebausteine ist erforderlich. Aber auch erfahrene Entwickler werden viele
Verwendungsmöglichkeiten für dieses Feature finden. Allgemeine Anweisungen bieten skriptähnliche
Experimentierfunktionen, ähnlich wie Jupyter Notebook-Instanzen. Allgemeine Anweisungen eignen sich auch
hervorragend für kleine Konsolenprogramme und Hilfsprogramme. Azure Functions ist ein idealer
Anwendungsfall für allgemeine Anweisungen.
Vor allem schränken allgemeine Anweisungen weder den Umfang noch die Komplexität einer Anwendung ein.
Diese Anweisungen können auf jede beliebige .NET-Klasse zugreifen oder diese verwenden. Außerdem
schränken sie nicht die Verwendung von Befehlszeilenargumenten oder Rückgabewerten ein. Allgemeine (top-
level) Anweisungen können auf ein Zeichenfolgenarray namens args zugreifen. Wenn allgemeine
Anweisungen einen ganzzahligen Wert zurückgeben, wird dieser Wert zum ganzzahligen Rückgabecode einer
synthetisierten Main -Methode. Allgemeine Anweisungen können async-Ausdrücke enthalten. In diesem Fall
gibt der synthetisierte Einstiegspunkt Task oder Task<int> zurück.
Weitere Informationen finden Sie unter Top-Level-Anweisungen im C#-Programmierleithandbuch.
Verbesserungen am Musterabgleich:
C# 9 enthält neue Verbesserungen am Musterabgleich:
Typmuster gleichen eine Variable mit einem Typ ab.
In Klammern gesetzte Muster erzwingen den Vorrang von Musterkombinationen oder heben diesen
hervor.
In konjunktiven and -Mustern müssen beide Muster übereinstimmen.
In disjunktiven or -Mustern muss eines von beiden Mustern übereinstimmen.
In negier ten not -Mustern darf ein Muster nicht übereinstimmen.
In relationalen Mustern muss die Eingabe kleiner als, größer als, kleiner gleich oder größer gleich einer
angegebenen Konstante sein.
Diese Muster erweitern die Mustersyntax. Sehen Sie sich die folgenden Beispiele an:
Mit optionalen Klammern, die verdeutlichen, dass and Vorrang vor or hat:
Einer der gängigsten Anwendungsfälle ist eine neue Syntax für NULL-Überprüfungen:
if (e is not null)
{
// ...
}
Jedes dieser Muster kann in jedem Kontext verwendet werden, in dem Muster zulässig sind: is -
Musterausdrücke, switch -Ausdrücke, geschachtelte Muster und das Muster einer case -Bezeichnung einer
switch -Anweisung.
Ganze Zahlen mit nativer Größe, nint und nuint , sind ganzzahlige Typen. Sie werden durch die zugrunde
liegenden Typen System.IntPtr und System.UIntPtr ausgedrückt. Der Compiler gibt zusätzliche Konvertierungen
und Vorgänge für diese Typen als native ganze Zahlen aus. Integer mit nativer Größe definieren die
Eigenschaften für MaxValue oder MinValue . Diese Werte können nicht als Kompilierzeitkonstanten ausgedrückt
werden, da sie von der nativen Größe einer ganzen Zahl auf dem Zielcomputer abhängen. Diese Werte sind zur
Laufzeit schreibgeschützt. Konstantenwerte können für nint in folgendem Bereich verwendet werden: [
int.MinValue ... int.MaxValue ]. Konstantenwerte können für nuint in folgendem Bereich verwendet werden: [
uint.MinValue ... uint.MaxValue ]. Der Compiler führt eine konstante Faltung aller unären und binären
Operatoren mithilfe der Typen System.Int32 und System.UInt32 durch. Wenn das Ergebnis nicht in 32 Bit passt,
wird der Vorgang zur Laufzeit ausgeführt und nicht als Konstante angesehen. Ganze Zahlen mit nativer Größe
können die Leistung in Szenarios steigern, in denen ganzzahlige Mathematik intensiv angewendet und die
schnellstmögliche Leistung benötigt wird. Weitere Informationen finden Sie unter den nint - und nuint -Typen.
Funktionszeiger bieten eine einfache Syntax für den Zugriff auf die IL-Opcodes ldftn und calli . Sie können
Funktionszeiger mithilfe der neuen delegate* -Syntax deklarieren. Ein delegate* -Typ ist ein Typ von Zeiger. Bei
einem Aufruf des delegate* -Typs wird calli verwendet. Dies ist ein Unterschied zu einem Delegaten, der
callvirt für die Invoke() -Methode verwendet. Syntaktisch sind die Aufrufe identisch. Bei Aufrufen von
Funktionszeigern wird die managed -Aufrufkonvention verwendet. Wenn Sie deklarieren möchten, dass Sie die
unmanaged -Aufrufkonvention benötigen, müssen Sie nach der delegate* -Syntax das Schlüsselwort unmanaged
einfügen. Andere Aufrufkonventionen können mithilfe von Attributen in der delegate* -Deklaration angegeben
werden. Weitere Informationen finden Sie unter Unsicherer Code und Zeigertypen.
Schließlich können Sie System.Runtime.CompilerServices.SkipLocalsInitAttribute hinzufügen, um den Compiler
anzuweisen, das localsinit -Flag nicht auszugeben. Dieses Flag weist die Common Language Runtime an, alle
lokalen Variablen mit 0 (Null) zu initialisieren. Das localsinit -Flag ist das Standardverhalten von C# seit
Version 1.0. Die zusätzliche Nullinitialisierung kann jedoch in einigen Szenarios zu nachweisbaren
Leistungseinbußen führen, insbesondere wenn Sie stackalloc verwenden. In diesen Fällen können Sie
SkipLocalsInitAttribute hinzufügen. Sie können die Klasse einer einzelnen Methode oder Eigenschaft, zu class /
struct / interface oder sogar zu einem Modul hinzufügen. Dieses Attribut hat keine Auswirkung auf abstract
-Methoden. Es beeinflusst den für die Implementierung generierten Code. Weitere Informationen finden Sie
unter SkipLocalsInit -Attribut.
Diese Features können die Leistung in einigen Szenarios verbessern. Sie sollten jedoch nur nach einem
sorgfältigen Leistungsvergleich vor und nach der Einführung eingesetzt werden. Code, der ganze Zahlen in
nativer Größe enthält, muss auf mehreren Zielplattformen mit unterschiedlichen Größen von ganzen Zahlen
getestet werden. Die anderen Features erfordern unsicheren Code.
Der Zieltyp new kann auch verwendet werden, wenn Sie ein neues Objekt erstellen müssen, das als Argument
an eine Methode übergeben werden soll. In diesem Fall können Sie eine ForecastFor() -Methode mit der
folgenden Signatur implementieren:
Ein weiterer nützlicher Anwendungsfall für dieses Feature ist die Kombination mit Nur-init-Eigenschaften, um
ein neues Objekt zu initialisieren:
Mithilfe einer return new(); -Anweisung können Sie eine Instanz zurückgeben, die vom Standardkonstruktor
erstellt wurde.
Ein ähnliches Feature verbessert die Zieltypauflösung von bedingten Ausdrücken. Aufgrund dieser Änderung
müssen die beiden Ausdrücke keine implizite Konvertierung von einem in den anderen aufweisen, sondern
können beide über implizite Konvertierungen in einen Zieltyp verfügen. Diese Änderung wird Ihnen
wahrscheinlich nicht auffallen. Was Sie bemerken werden, ist, dass einige bedingte Ausdrücke, die zuvor eine
Umwandlung erforderten oder nicht kompiliert werden konnten, jetzt funktionieren.
Ab C# 9.0 können Sie Lambdaausdrücken oder anonymen Methoden den Modifizierer static hinzufügen.
Statische Lambdaausdrücke entsprechen den lokalen static -Funktionen: Eine statische Lambdafunktion oder
anonyme Methode kann weder lokale Variablen noch den Instanzzustand erfassen. Der Modifizierer static
verhindert, dass versehentlich andere Variablen erfasst werden.
Kovariante Rückgabetypen flexibilisieren die Rückgabetypen von override-Methoden. Eine override-Methode
kann einen Typ zurückgeben, der vom Rückgabetyp der überschriebenen Basismethode abgeleitet wurde. Dies
kann sowohl für Datensätze als auch für andere Typen nützlich sein, die virtuelle Klon- oder Factorymethoden
unterstützen.
Außerdem erkennen und verwenden foreach -Schleifen eine GetEnumerator -Erweiterungsmethode, die
ansonsten das foreach -Muster erfüllt. Diese Änderung bedeutet, dass foreach mit anderen musterbasierten
Konstruktionen, z. B. mit dem async-Muster, sowie der musterbasierten Dekonstruktion konsistent ist. In der
Praxis bedeutet diese Änderung, dass Sie jedem Typ foreach -Unterstützung hinzufügen können. Sie sollten die
Verwendung von „foreach“ jedoch auf die Fälle beschränken, in denen die Enumeration eines Objekts in Ihrem
Softwareentwurf sinnvoll ist.
Sie können auch Ausschussvariablen als Parameter für Lambdaausdrücke verwenden. So müssen Sie das
Argument nicht mehr benennen, und der Compiler muss es unter Umständen gar nicht verwenden. Sie nutzen
einfach _ für alle Argumente. Weitere Informationen finden Sie im Abschnitt Eingabeparameter eines
Lambdaausdrucks des Artikels Lambdaausdrücke.
Schließlich haben Sie nun die Möglichkeit, Attribute auf lokale Funktionen anzuwenden. Sie können
beispielsweise Nullable-Attributanmerkungen auf lokale Funktionen anwenden.
C# 8.0 fügt der Sprache C# die folgenden Features und Verbesserungen hinzu:
Readonly-Member
Standardschnittstellenmethoden
Verbesserungen am Musterabgleich:
switch-Ausdrücke
Eigenschaftsmuster
Tupelmuster
Positionsmuster
using-Deklarationen
Statische lokale Funktionen
Verwerfbare Referenzstrukturen
Nullwerte zulassende Verweistypen
Asynchrone Streams
Asynchrone verwerfbare Typen
Indizes und Bereiche
NULL-Coalescing-Zuweisung
Nicht verwaltete konstruierte Typen
Stackalloc in geschachtelten Ausdrücken
Erweiterung von interpolierten ausführlichen Zeichenfolgen
C# 8.0 wird unter .NET Core 3.x und .NET Standard 2.1 unterstützt. Weitere Informationen finden Sie unter
C#-Sprachversionsverwaltung.
Der Rest dieses Artikels beschreibt diese Funktionen kurz. Wenn ausführliche Artikel verfügbar sind, werden
Links zu diesen Tutorials und Übersichten bereitgestellt. Sie können sich diese Funktionen in unserer Umgebung
mit dem globalen dotnet try -Tool näher ansehen:
1. Installieren Sie das globale dotnet-try-Tool.
2. Klonen Sie das dotnet/try-samples-Repository.
3. Legen Sie das aktuelle Verzeichnis auf das Unterverzeichnis csharp8 für das try-samples-Repository fest.
4. Führen Sie aus dotnet try .
Readonly-Member
Sie können den readonly -Modifizierer auf jeden Member einer Struktur anwenden. Damit wird angezeigt, dass
der Member den Zustand nicht ändert. Dies ist granularer als das Anwenden des readonly -Modifikators auf
eine struct -Deklaration. Betrachten Sie folgende veränderliche Struktur:
public struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);
Wie bei den meisten Strukturen verändert die ToString() -Methode den Zustand nicht. Sie könnten dies durch
Hinzufügen des readonly -Modifikators zur Deklaration von ToString() angeben:
Die vorhergehende Änderung generiert eine Compilerwarnung, weil ToString auf die Distance -Eigenschaft
zugreift, die nicht als readonly markiert ist:
warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an
implicit copy of 'this'
Der Compiler warnt Sie, wenn er eine Defensivkopie erstellen muss. Die Distance -Eigenschaft verändert nicht
den Zustand, sodass Sie diese Warnung aufheben können, indem Sie der Deklaration den readonly -
Modifizierer hinzufügen:
Beachten Sie, dass der readonly -Modifizierer bei einer schreibgeschützten Eigenschaft erforderlich ist. Der
Compiler geht nicht davon aus, dass get -Zugriffsmethoden den Zustand nicht ändern. Sie müssen readonly
explizit deklarieren. Automatisch implementierte Eigenschaften sind eine Ausnahme; der Compiler behandelt
alle automatisch implementierten Getter als readonly , sodass es hier nicht notwendig ist, den readonly -
Modifizierer zu den X - und Y -Eigenschaften hinzuzufügen.
Der Compiler erzwingt die Regel, dass readonly -Member den Status nicht ändern. Die folgende Methode wird
nicht kompiliert, es sei denn, Sie entfernen den readonly -Modifizierer:
Mit diesem Feature können Sie Ihre Designabsicht angeben, damit der Compiler sie erzwingen und
Optimierungen basierend auf dieser Absicht vornehmen kann.
Weitere Informationen finden Sie im Abschnitt readonly -Instanzmember des Artikels Strukturtypen.
Standardschnittstellenmethoden
Sie können nun Member zu Schnittstellen hinzufügen und eine Implementierung für diese Member
bereitstellen. Dieses Sprachfeature ermöglicht es API-Autoren, in späteren Versionen Methoden zu einer
Schnittstelle hinzuzufügen, ohne die Quell- oder Binärkompatibilität mit bestehenden Implementierungen dieser
Schnittstelle zu beeinträchtigen. Bestehende Implementierungen erben die Standardimplementierung. Dieses
Feature ermöglicht zudem die Interaktion zwischen C# und APIs, die auf Android oder Swift abzielen und
ähnliche Funktionen unterstützen. Standardschnittstellenmethoden ermöglichen auch Szenarien, die einem
„Traits“-Sprachfeature ähneln.
Standardschnittstellenmethoden wirken sich auf viele Szenarien und Sprachelemente aus. Unser erstes Tutorial
behandelt die Aktualisierung einer Schnittstelle mit Standardimplementierungen.
Wenn Ihre Anwendung einen RGBColor -Typ definiert hat, der aus den Komponenten R , G und B aufgebaut
ist, können Sie einen Rainbow -Wert in seine RGB-Werte konvertieren, indem Sie die folgende Methode
verwenden, die einen Switch-Ausdruck enthält:
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName:
nameof(colorBand)),
};
Die Meldungen zeigen den Gewinner an. Der Fall „Verwerfen“ („case discard“) stellt die drei Kombinationen für
Unentschieden oder andere Texteingaben dar.
Positionsmuster
Einige Typen umfassen eine Deconstruct -Methode, die ihre Eigenschaften in diskrete Variablen dekonstruiert.
Wenn auf eine Deconstruct -Methode zugegriffen werden kann, können Sie Positionsmuster verwenden, um
Eigenschaften des Objekts zu untersuchen, und diese Eigenschaften für ein Muster verwenden. Beachten Sie die
folgende Point -Klasse, die eine Deconstruct -Methode umfasst, um diskrete Variablen für X und Y zu
erstellen:
Beachten Sie zudem die folgende Enumeration, die verschiedene Positionen eines Quadranten darstellt:
public enum Quadrant
{
Unknown,
Origin,
One,
Two,
Three,
Four,
OnBorder
}
Die folgende Methode verwendet das Positionsmuster , um die Werte von x und y zu extrahieren. Dann
wird mit einer when -Klausel der Quadrant des Punktes bestimmt:
Das Ausschussmuster im vorherigen Switch stimmt überein, wenn entweder x oder y 0 ist, jedoch nicht
beide. Ein switch-Ausdruck muss entweder einen Wert erzeugen oder eine Ausnahme auslösen. Wenn keiner
der Fälle übereinstimmt, löst der switch-Ausdruck eine Ausnahme aus. Der Compiler erzeugt für Sie eine
Warnung, wenn Sie nicht alle möglichen Fälle in Ihrem Switch-Ausdruck abdecken.
In diesem erweiterten Tutorial zum Musterabgleich erhalten Sie weitere Informationen zu
Musterabgleichverfahren. Weitere Informationen zu einem Positionsmuster finden Sie im Abschnitt
Positionsmuster des Artikels Muster.
Using-Deklarationen
Eine using-Deklaration ist eine Variablendeklaration, der das Schlüsselwort using vorangestellt ist. Es teilt
dem Compiler mit, dass die zu deklarierende Variable am Ende des umschließenden Bereichs angeordnet
werden soll. Sehen Sie sich beispielsweise den folgenden Code an, der eine Textdatei schreibt:
Im vorhergehenden Beispiel wird die Datei angeordnet, wenn die der using -Anweisung zugeordnete
schließende Klammer erreicht ist.
In beiden Fällen generiert der Compiler den Aufruf von Dispose() . Der Compiler erzeugt einen Fehler, wenn der
Ausdruck in der using -Anweisung nicht verwerfbar ist.
int M()
{
int y;
LocalFunction();
return y;
Der folgende Code enthält eine statische lokale Funktion. Er kann statisch sein, da er nicht auf Variablen im
umschließenden Bereich zugreift:
int M()
{
int y = 5;
int x = 7;
return Add(x, y);
Verwerfbare Referenzstrukturen
Ein mit dem ref -Modifizierer deklarierter struct darf keine Schnittstellen und damit auch keine IDisposable
implementieren. Aus diesem Grund Aktivieren einer ref struct um verworfen werden, muss eine zugängliche
void Dispose() Methode. Dieses Feature gilt auch für readonly ref struct -Deklarationen.
Asynchrone Streams
Ab C# 8.0 können Sie Streams asynchron erstellen und nutzen. Eine Methode, die einen asynchronen Stream
zurückgibt, hat drei Eigenschaften:
1. Die Deklaration erfolgte mit dem async -Modifikator.
2. Es wird IAsyncEnumerable<T> zurückgegeben.
3. Das Verfahren enthält yield return -Anweisungen, um aufeinanderfolgende Elemente im asynchronen
Stream zurückzugeben.
Die Verwendung eines asynchronen Streams erfordert, dass Sie das Schlüsselwort await vor dem
Schlüsselwort foreach hinzufügen, wenn Sie die Elemente des Streams auflisten. Das Hinzufügen des
Schlüsselwortes await erfordert, dass die Methode, die den asynchronen Strom enumiert, mit dem Modifikator
async deklariert wird und einen für eine async -Methode zulässigen Typ zurückgibt. In der Regel ist dies die
Rückgabe von Task oder Task<TResult>. Es kann auch ValueTask oder ValueTask<TResult> sein. Eine Methode
kann einen asynchronen Stream sowohl verwenden als auch erzeugen, was bedeutet, dass sie
IAsyncEnumerable<T> zurückgeben würde. Der folgende Code erzeugt eine Sequenz von 0 bis 19 und wartet
100 ms zwischen der Generierung jeder Zahl:
Die Enumeration der Sequenz erfolgt mit der await foreach -Anweisung:
Sie können asynchrone Streams selbst in unserem Tutorial zum Erstellen und Verwenden von asynchronen
Streams ausprobieren. Standardmäßig werden Streamelemente im erfassten Kontext verarbeitet. Wenn Sie die
Erfassung des Kontexts deaktivieren möchten, verwenden Sie die Erweiterungsmethode
TaskAsyncEnumerableExtensions.ConfigureAwait. Weitere Informationen über Synchronisierungskontexte und
die Erfassung des aktuellen Kontexts finden Sie im Artikel über das Verwenden des aufgabenbasierten
asynchronen Musters.
Ein Bereich gibt den Beginn und das Ende eines Bereichs an. Der Beginn des Bereichs ist inklusiv, das Ende des
Bereichs ist jedoch exklusiv. Das bedeutet, dass der Beginn im Bereich enthalten ist, das Ende aber nicht. Der
Bereich [0..^0] stellt ebenso wie [0..sequence.Length] den gesamten Bereich dar.
Schauen wir uns einige Beispiele an. Betrachten Sie das folgende Array, kommentiert mit seinem Index „from
the start“ und „from the end“:
var words = new string[]
{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0
Der folgende Code erzeugt einen Teilbereich mit den Worten „quick“, „brown“ und „fox“. Er enthält words[1] bis
words[3] . Das Element words[4] befindet sich nicht im Bereich.
Der folgende Code erzeugt einen Teilbereich mit „lazy“ und „dog“. Dazu gehören words[^2] und words[^1] . Der
Endindex words[^0] ist nicht enthalten:
Die folgenden Beispiele erstellen Bereiche, die am Anfang, am Ende und auf beiden Seiten offen sind:
Der Bereich kann dann innerhalb der Zeichen [ und ] verwendet werden:
Indizes und Bereiche werden nicht nur von Arrays unterstützt. Indizes und Bereiche können auch mit string,
Span<T> oder ReadOnlySpan<T> verwendet werden. Weitere Informationen finden Sie unter Typunterstützung
für Indizes und Bereiche.
Weitere Informationen zu Indizes und Bereichen finden Sie im Tutorial zu Indizes und Bereichen.
NULL-Coalescing-Zuweisung
In C# 8.0 wird der NULL-Coalescing-Zuweisungsoperator ??= eingeführt. Sie können den ??= -Operator
verwenden, um den Wert des rechten Operanden dem linken Operanden nur dann zuzuweisen, wenn die
Auswertung des linken Operanden null ergibt.
Dann ist der Coords<int> -Typ in C# 8.0 und höher ein nicht verwalteter Typ. Wie bei allen nicht verwalteten
Typen können Sie einen Zeiger auf eine Variable dieses Typs erstellen oder Instanzen dieses Typs einen
Arbeitsspeicherblock im Stapel zuordnen:
Von C# 7.0 bis C# 7.3 wurden zahlreiche Features für und inkrementelle Verbesserungen an den
Entwicklungsfunktionen von C# eingeführt. In diesem Artikel erhalten Sie einen Überblick über die neuen
Sprachfeatures und Compileroptionen. In den Beschreibungen wird das Verhalten für C# 7.3 erläutert. Dabei
handelt es sich um die aktuelle Version, die für auf dem .NET Framework basierende Anwendungen unterstützt
wird.
Das Konfigurationselement für die Sprachversionsauswahl wurde im Rahmen von C# 7.1 hinzugefügt. Damit
können Sie die Compilersprachenversion in Ihrer Projektdatei angeben.
Von C# 7.0 bis C# 7.3 wurden der C#-Sprache die folgenden Features und Designs hinzugefügt:
Tupel und Verwerfen
Sie können einfache, unbenannte Typen erstellen, die mehrere öffentliche Felder enthalten. Compiler
und IDE-Tools kennen die Semantik dieser Typen.
Ausschussvariablen (discards) sind temporäre, lesegeschützte Variablen, die in Zuweisungen
verwendet werden, wenn der zugewiesene Wert nicht weiter interessiert. Sie eignen sich besonders
zum Dekonstruieren von Tupeln und benutzerdefinierten Typen sowie beim Aufrufen von Methoden
mit out -Parametern.
Mustervergleich
Sie können Verzweigungslogik basierend auf beliebigen Typen und Werten der Member dieser Typen
erstellen.
async Main -Methode
Der Einstiegspunkt für eine Anwendung kann über den Modifizierer async verfügen.
Lokale Funktionen
Sie können Funktionen innerhalb von anderen Funktionen verschachteln, um deren Bereich und
Sichtbarkeit zu beschränken.
Mehr Ausdruckskörpermember
Die Liste der Member, die mithilfe von Ausdrücken erstellt werden können, ist länger geworden.
throw -Ausdrücke
Sie können Ausnahmen in Codekonstrukten auslösen, die vorher nicht zulässig waren, da throw eine
Anweisung war.
default Literale Ausdrücke
Sie können literale Standardausdrücke in Standardwertausdrücken verwenden, wenn der Zieltyp
abgeleitet werden kann.
Verbesserung der numerischen literalen Syntax
Neue Token verbessern die Lesbarkeit für numerische Konstanten.
out Variablen
Sie können out -Werte als Inlineargumente für die Methode deklarieren, wenn sie verwendet werden.
Nicht schließende benannte Argumente
Positionelle Argumente können auf benannte Argumente folgen.
private protected -Zugriffsmodifizierer
Der private protected -Zugriffsmodifizierer ermöglicht den Zugriff für abgeleitete Klassen innerhalb
der gleichen Assembly.
Verbesserte Überladungsauflösung
Neue Regeln sorgen nun für die Auflösung von Ambiguitäten bei Überladungsauflösungen.
Techniken zum Schreiben von sicherem, effizientem Code
Eine Kombination aus Verbesserungen der Syntax, die das Arbeiten mit Werttypen mithilfe von
Verweissemantik ermöglichen.
Schließlich verfügt der Compiler über neue Optionen:
-refout und -refonly , wodurch die Erstellung von Referenzassemblys gesteuert wird.
-publicsign , um das Signieren von Assemblys durch Open Source Software (OSS) zu ermöglichen.
-pathmap , um eine Zuordnung für Quellverzeichnisse bereitzustellen.
Dieser Artikel enthält im Folgenden eine Übersicht über die einzelnen Funktionen. Sie werden die Hintergründe
sowie die Syntax jedes einzelnen Features kennenlernen. Sie können sich diese Funktionen in unserer
Umgebung mit dem globalen dotnet try -Tool näher ansehen:
1. Installieren Sie das globale dotnet-try-Tool.
2. Klonen Sie das dotnet/try-samples-Repository.
3. Legen Sie das aktuelle Verzeichnis auf das Unterverzeichnis csharp7 für das try-samples-Repository fest.
4. Führen Sie aus dotnet try .
NOTE
Tupel waren schon vor C# 7.0 verfügbar, sie waren jedoch ineffizient und hatten keine Sprachunterstützung. Das brachte
mit sich, dass auf Tupelelemente nur als Item1 , Item2 usw. verwiesen werden konnte. Mit C# 7.0 wird
Sprachunterstützung für Tupel eingeführt, wodurch semantische Namen für die Felder eines Tupels mit Einsatz neuer,
effizienterer Tupeltypen möglich werden.
Sie können ein Tupel erstellen, indem Sie jedem Member einen Wert zuweisen und ihnen optional auch
semantische Namen bereitstellen:
Das namedLetters -Tupel enthält Felder, die als Alpha und Beta bezeichnet werden. Diese Namen bestehen nur
zur Kompilierzeit und werden nicht beibehalten, wenn das Tupel beispielsweise zur Laufzeit mithilfe von
Reflektion untersucht wird.
In einer Tupelzuweisung können Sie auch die Namen der Felder auf der rechten Seite der Zuweisung angeben:
Manchmal möchten Sie vielleicht die Member eines Tupels entpacken, die von einer Methode zurückgegeben
wurden. Sie können dazu für jeden Wert im Tupel separate Variablen deklarieren. Das Auspacken wird als
Dekonstruieren des Tupels bezeichnet:
Sie können auch eine ähnliche Dekonstruktion für alle Typen in .NET bereitstellen. Schreiben Sie eine
Deconstruct -Methode als Member der Klasse. Diese Deconstruct -Methode bietet eine Reihe von out -
Argumenten für jede der Eigenschaften, die Sie extrahieren möchten. Berücksichtigen Sie diese Point -Klasse,
die eine Dekonstruktionsmethode bereitstellt, die die X - und Y -Koordinaten extrahiert:
Sie können die einzelnen Felder extrahieren, indem Sie einem Point ein Tupel zuweisen:
Wenn Sie einen Tupel initialisieren, sind die Variablen, die für die rechte Seite der Zuweisung verwendet werden,
oft dieselben, wie die Namen, die Sie den Tupelelementen geben möchten. Die Namen von Tupelelementen
können von den Variablen abgeleitet werden, die zum Initialisieren der Tupel verwendet werden:
int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"
using System;
private static (string name, int pop, double size) QueryCityData(string name)
{
if (name == "New York City")
return (name, 8175133, 468.48);
Musterabgleich
Beim Musterabgleich handelt es sich um mehrere Features, die neue Möglichkeiten eröffnen, Ablaufsteuerung in
Ihrem Code auszudrücken. Sie können Variablen nach Typ, Werten oder den Werten ihrer Eigenschaften testen.
Dieses Vorgehen sorgt für einen besser lesbaren Codeflow.
Mustervergleich unterstützt is -Ausdrücke und switch -Ausdrücke. Jeder davon ermöglicht das Überprüfen
eines Objekts und dessen Eigenschaften, um zu bestimmen, ob das Objekt dem gesuchten Muster entspricht. Sie
verwenden das when -Schlüsselwort, um zusätzliche Regeln für das Muster anzugeben.
Der Musterausdruck is erweitert den vertrauten is -Operator, um eine Abfrage zum Typ eines Objekts
auszuführen und das Ergebnis in einer Anweisung zuzuweisen. Der folgende Code überprüft, ob es sich bei
einer Variablen um einen int -Wert handelt und fügt sie, wenn dies der Fall ist, der aktuellen Summe hinzu:
Das vorausgegangene kleine Beispiel verdeutlicht die Verbesserungen des is -Ausdrucks. Sie können für Wert-
und Verweistypen testen und das erfolgreiche Ergebnis einer neuen Variable des richtigen Typs zuweisen.
Der Switch-Vergleichsausdruck verfügt über eine vertraute Syntax basierend auf der switch -Anweisung, die
bereits Teil der C#-Sprache ist. Die aktualisierte Switch-Anweisung verfügt über mehrere neue Konstrukte:
Der maßgebliche Typ eines switch -Ausdrucks ist nicht mehr beschränkt auf ganzzahlige Typen, Enum -
Typen, string oder einen Nullable-Typ, der einem dieser Typen entspricht. Es kann jeder Typ verwendet
werden.
Sie können den Typ des switch -Ausdrucks in jeder case -Bezeichnung testen. Sie können diesem Typ wie
schon beim is -Ausdruck eine neue Variable zuweisen.
Wenn Sie die Bedingungen für diese Variable weiter testen möchten, können Sie eine when -Klausel
hinzufügen.
Die Reihenfolge der case -Bezeichnungen ist hierbei entscheidend. Die erste Verzweigung, für die eine
Übereinstimmung gefunden wird, wird ausgeführt. Alle weiteren werden übersprungen.
Im folgenden Code werden diese Funktionen veranschaulicht:
Ab C# 7.1 kann der Musterausdruck für is und das switch -Typmuster den Typ eines generischen
Typparameters haben. Dies kann sehr nützlich sein, wenn Sie Typen überprüfen, die entweder struct - oder
class -Typen sein können, und Sie Boxing vermeiden möchten.
Weitere Informationen zum Mustervergleich finden Sie unter Pattern Matching in C# (Mustervergleich in C#).
Async Main
Mithilfe einer async main-Methode können Sie await in Ihrer Main -Methode verwenden. Vorher hätten Sie
das Folgende schreiben müssen:
Wenn Ihr Programm keinen Exitcode zurückgibt, können Sie eine Main -Methode deklarieren, die einen Task
zurückgibt:
Lokale Funktionen
Viele Entwürfe für Klassen enthalten Methoden, die nur von einer Stelle aufgerufen werden. Durch diese
zusätzlichen privaten Methoden bleibt jede Methode klein und konzentriert. Mit lokalen Funktionen können Sie
Methoden innerhalb des Kontexts einer anderen Methode deklarieren. So können Leser der Klasse leichter
erkennen, dass die lokale Methode nur aus dem Kontext aufgerufen wird, in dem sie deklariert wird.
Es gibt zwei häufige Anwendungsfälle für lokale Funktionen: öffentliche Iteratormethoden und öffentliche
asynchrone Methoden. Beide Arten von Methoden generieren Code, der Fehler später meldet, als
Programmierer erwarten würden. Bei Iteratormethoden werden Ausnahmen nur festgestellt, wenn Code
aufgerufen wird, der die zurückgegebene Sequenz auflistet. Bei asynchronen Methoden werden Ausnahmen nur
festgestellt, wenn der zurückgegebene Task -Wert erwartet wird. Im folgenden Beispiel wird veranschaulicht,
wie mithilfe einer lokalen Funktion die Überprüfung der Parameter von der Iteratorimplementierung getrennt
wird:
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
Das gleiche Verfahren kann mit async -Methoden eingesetzt werden, um sicherzustellen, dass Ausnahmen
aufgrund der Argumentüberprüfung ausgelöst werden, bevor die asynchrone Arbeit beginnt:
public Task<string> PerformLongRunningWork(string address, int index, string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-
negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
return longRunningWorkImplementation();
[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }
Das Attribut wird auf das vom Compiler generierte Unterstützungsfeld für
SomeThingAboutFieldAttribute
SomeProperty angewendet. Weitere Informationen finden Sie unter attributes im C#-Programmierhandbuch.
NOTE
Einige der Entwürfe, die von lokalen Funktionen unterstützt werden, können auch mithilfe von Lambdaausdrücken
erreicht werden. Weitere Informationen finden Sie unter Lokale Funktionen im Vergleich zu Lambdaausdrücken.
Mehr Ausdruckskörpermember
In C# 6 wurden Ausdruckskörpermember für Memberfunktionen und schreibgeschützte Eigenschaften
eingeführt. Mit C# 7.0 werden die zulässigen Member erweitert, die als Ausdrücke implementiert werden
können. In C# 7.0 können Sie Konstruktoren, Finalizer sowie get - und set -Zugriffsmethoden für
Eigenschaften und Indexer implementieren. Der folgende Code zeigt entsprechende Beispiele:
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
Diese neuen Speicherorte für Member mit Ausdruckskörper sind ein wichtiger Meilenstein für die Sprache C#:
Diese Features wurden von Community-Mitgliedern implementiert, die am Open Source-Projekt Roslyn
arbeiten.
Das Ändern einer Methode in ein Ausdruckskörpermember ist eine binärkompatible Änderung.
Throw-Ausdrücke
In C# ist throw schon immer eine Anweisung. Da throw eine Anweisung und kein Ausdruck ist, gab es C#-
Konstrukte, in denen diese Anweisung nicht verwendet werden konnte. Darunter waren bedingte Ausdrücke,
NULL-Sammelausdrücke und einige Lambdaausdrücke. Das Hinzufügen von Ausdruckskörpermembern fügt
mehr Speicherorte hinzu, bei denen throw -Ausdrücke nützlich wären. Damit Sie diese Konstrukte schreiben
können, wurden in C# 7.0 Throw-Ausdrücke eingeführt.
Diese Ergänzung erleichtert das Schreiben ausdrucksbasierteren Codes. Sie benötigen zur Fehlerüberprüfung
keine weiteren Anweisungen.
Literale Standardausdrücke
Literale Standardausdrücke sind eine Erweiterung von Ausdrücken mit Standardwert. Diese Ausdrücke
initialisieren eine Variable auf dem Standardwert. Dort, wo Sie vorher das Folgende geschrieben haben:
Können Sie nun den Typ weglassen, der auf der rechten Seite der Initialisierung steht.
Weitere Informationen finden Sie im Abschnitt Standardliteral des Artikels zum default-Operator.
Das 0b am Anfang der Konstante gibt an, dass die Zahl als binäre Zahl geschrieben wird. Binäre Zahlen können
lang werden. Daher kann die Einführung von _ als Ziffertrennzeichen wie in der binären Konstante im
vorherigen Beispiel gezeigt die Bitmuster übersichtlicher machen. Das Zifferntrennzeichen kann überall in der
Konstante angezeigt werden. Für Zahlen der Basis 10 wird es üblicherweise als Tausendertrennzeichen
verwendet. Hexadezimale und binäre numerische Literale dürfen jetzt mit _ beginnen:
Das Zifferntrennzeichen kann auch mit decimal -, float - und double -Typen verwendet werden:
out -Variablen
Die vorhandene Syntax zur Unterstützung von out -Parametern wurde in C# 7 verbessert. Nun können Sie out
-Variablen in der Argumentliste eines Methodenaufrufs deklarieren, anstatt eine separate
Deklarationsanweisung zu schreiben:
Sie sollten den Typ der out -Variablen aus Gründen der Übersichtlichkeit angeben. Dies wird im vorherigen
Beispiel veranschaulicht. Die Sprache unterstützt jedoch die Verwendung einer implizit typisierten lokalen
Variable:
public class D : B
{
public D(int i) : base(i, out var j)
{
Console.WriteLine($"The value of 'j' is {j}");
}
}
Verbesserte Überladungskandidaten
In jedem Release werden die Überladungsauflösungsregeln für Situationen aktualisiert, in denen mehrdeutige
Methodenaufrufe eine „naheliegende“ Auswahlmöglichkeit haben. In diesem Release werden drei neue Regeln
hinzufügt, damit der Compiler die naheliegende Auswahlmöglichkeit nutzt:
1. Wenn eine Methodengruppe sowohl Instanz- als auch statische Member enthält, verwirft der Compiler die
Instanzmember, wenn die Methode ohne Instanzempfänger oder -kontext aufgerufen wurde. Der Compiler
verwirft die statischen Member, wenn die Methode mit einem Instanzempfänger aufgerufen wurde. Wenn
kein Empfänger vorhanden ist, bezieht der Compiler nur statische Member in einen statischen Kontext ein –
andernfalls sowohl statische als auch Instanzmember. Wenn unklar ist, ob der Empfänger eine Instanz oder
ein Typ ist, bezieht der Compiler beides ein. Ein statischer Kontext, wo ein impliziter this -Instanzempfänger
nicht verwendet werden kann, enthält den Text von Membern, wo kein this definiert ist, z.B. statische
Member, sowie Orte, an denen this nicht verwendet werden kann, z.B. Feld- und Konstruktorinitialisierer.
2. Wenn eine Methodengruppe einige generische Methoden enthält, deren Typargumente ihre
Einschränkungen nicht erfüllen, werden diese Member aus dem Satz von Kandidaten entfernt.
3. Für eine Methodengruppenkonvertierung werden Kandidatenmethoden, deren Rückgabetyp nicht mit dem
des Delegaten übereinstimmt, aus dem Satz entfernt.
Sie werden diese Änderung bemerken, da Sie weniger Compilerfehler für mehrdeutige Methodenüberladungen
finden werden, wenn Sie sicher sind, welche Methode besser ist.
Sie können den Rückgabewert als ref deklarieren und diesen Wert wie im folgenden Code gezeigt in der
Matrix ändern:
Weitere Informationen finden Sie im Artikel zu ref -Rückgaben und lokalen ref -Variablen und im Artikel zu
foreach .
Die Variable r ist ein Verweis auf den ersten Wert in arr oder otherArr .
Weitere Informationen finden Sie unter conditional-Operator (?:) in der Sprachreferenz.
Modifizierer für in -Parameter
Das in -Schlüsselwort ergänzt die vorhandenen Schlüsselwörter ref und out zum Übergeben von Argumenten
als Verweis. Durch das in -Schlüsselwort wird festgelegt, dass das Argument als Verweis übergeben wird, die
aufgerufene Methode aber nicht den Wert ändert.
Sie können Überladungen, die nach Wert oder nach schreibgeschütztem Verweis übergeben werden, wie im
folgenden Code gezeigt deklarieren:
Die Überladung, die nach Wert übergeben wird (die erste im obigen Beispiel) ist besser als die Version, die nach
schreibgeschütztem Verweis übergeben wird. Um die Version mit dem readonly-Verweisargument aufzurufen,
müssen Sie auch den in -Modifizierer beim Aufrufen der Methode einbeziehen.
Weitere Informationen finden Sie im Artikel zum in -Parametermodifizierer.
Weitere Typen unterstützen die fixed -Anweisung
Die -Anweisung unterstützte eine begrenzte Anzahl von Typen. Ab C# 7.3 kann jeder Typ, der eine
fixed
GetPinnableReference() -Methode enthält, die eine ref T oder ref readonly T zurückgibt, fixed sein. Dieses
Feature hinzuzufügen, bedeutet, dass fixed mit System.Span<T> und verwandten Typen genutzt werden kann.
Weitere Informationen finden Sie im Artikel zur fixed -Anweisung in der Sprachreferenz.
Indizieren von fixed -Feldern erfordert kein Anheften
Betrachten Sie diese Struktur:
unsafe struct S
{
public fixed int myFixedField[10];
}
In früheren Versionen von C# mussten Sie eine Variable für den Zugriff auf eine der ganzen Zahlen anheften, die
Teil von myFixedField sind. Der folgende Code wird kompiliert, ohne die Variable p in einer separaten fixed -
Anweisung anzuheften:
class C
{
static S s = new S();
Die Variable p greift auf ein Element in myFixedField zu. Sie müssen keine separate int* -Variable
deklarieren. Sie benötigen weiterhin einen unsafe -Kontext. In früheren Versionen von C# müssen Sie einen
zweiten festen Zeiger deklarieren:
class C
{
static S s = new S();
Nun kann die gleiche Syntax auf Arrays angewendet werden, die mit stackalloc deklariert werden:
NOTE
Sie müssen das NuGet-Paket System.Threading.Tasks.Extensions hinzufügen, um den Typ ValueTask<TResult>
verwenden zu können.
Diese Verbesserung ist besonders für Bibliotheksautoren hilfreich, um die Zuordnung von Task in
leistungskritischem Code zu verhindern.
Neue Compileroptionen
Neue Compileroptionen unterstützen neue Build- und DevOpsszenarien für C#-Programme.
Generierung der Referenzassembly
Es gibt zwei neue Compileroptionen, die reine Verweisassemblys generieren: ProduceReferenceAssembly
und ProduceOnlyReferenceAssembly . In den verlinkten Artikeln werden diese Optionen und
Referenzassemblys ausführlich beschrieben.
Öffentliche oder Open Source -Signierung
Die Compileroption PublicSign weist den Compiler an, die Assembly mit einem öffentlichen Schlüssel zu
signieren. Die Assembly wird als signiert markiert, aber die Signatur wird dem öffentlichen Schlüssel
entnommen. Mit dieser Option können Sie signierte Assemblys aus Open Source-Projekten mit einem
öffentlichen Schlüssel erstellen.
Weitere Informationen finden Sie im Artikel zur PublicSign -Compileroption.
pathmap
Die Compileroption PathMap weist den Compiler an, Quellpfade aus der Buildumgebung mit zugeordneten
Quellpfaden zu ersetzen. Die Option PathMap CallerFilePathAttribute steuer t den Quellpfad, der vom
Compiler in PDB-Dateien oder für geschrieben wird.
Weitere Informationen finden Sie im Artikel zur PathMap -Compileroption.
Informationen zu Breaking Changes im C#-
Compiler
04.11.2021 • 2 minutes to read
Das Roslyn-Team verwaltet eine Liste von Breaking Changes in den C#- und Visual Basic-Compilern.
Informationen zu diesen Änderungen finden Sie unter diesen Links im GitHub-Repository:
Breaking Changes in Roslyn nach .NET 5
Breaking Changes in VS2019 Version 16.8, eingeführt in .NET 5.0 und C# 9.0
Breaking Changes in VS2019 Update 1 oder höher im Vergleich zu VS2019
Breaking Changes seit VS2017 (C# 7)
Breaking Changes in Roslyn 3.0 (VS2019) im Vergleich zu Roslyn 2.* (VS2017)
Breaking Changes in Roslyn 2.0 (VS2017) im Vergleich zu Roslyn 1.* (VS2015) und zum nativen C#-Compiler
(VS2013 oder früher).
Breaking Changes in Roslyn 1.0 (VS2015) im Vergleich zum nativen C#-Compiler (VS2013 oder früher).
Änderung der Unicode-Version in C# 6
Die Geschichte von C#
04.11.2021 • 12 minutes to read
Dieser Artikel erläutert den Verlauf jeder Hauptversion der Sprache C#. Das C#-Team entwickelt und ergänzt
immer neue und innovative Features. Informationen zu den Status von Programmiersprachenfunktionen, so
auch Funktionen, die für zukünftige Versionen geplant sind, finden Sie im dotnet/roslyn-Repository auf GitHub.
IMPORTANT
Für einige der Features nutzt die Sprache C# Typen und Methoden in einer Struktur, die in der C#-Spezifikation als
Standardbibliothek bezeichnet wird. Die .NET-Plattform stellt diese Typen und Methoden in verschiedenen Paketen bereit.
Ein Beispiel ist die Ausnahmeverarbeitung. Alle throw -Anweisungen oder -Ausdrücke werden überprüft, um
sicherzustellen, dass das ausgelöste Objekt von Exception abgeleitet ist. Auf ähnliche Weise wird jedes catch überprüft,
um sicherzustellen, dass der abgefangen Typ von Exception abgeleitet ist. Jede Version kann neue Anforderungen
hinzufügen. Um die neuesten Sprachfunktionen in älteren Umgebungen verwenden zu können, müssen Sie vielleicht
bestimmte Bibliotheken installieren. Diese Abhängigkeiten werden auf der jeweiligen Seite für eine spezifische Version
dokumentiert. Sie können mehr über die Beziehungen zwischen Sprache und Bibliothek erfahren, um
Hintergrundinformationen zu dieser Abhängigkeit zu erhalten.
C# Version 1.0
Die mit Visual Studio .NET 2002 veröffentlichte C#-Version 1.0 ähnelte Java sehr stark. Als Teil der
vorgegebenen Entwurfsziele für ECMA sollte sie eine „einfache, moderne, objektorientierte Universalsprache
sein“. Zu diesem Zeitpunkt bedeuteten die Ähnlichkeiten mit Java, dass die frühen Entwurfsziele erreicht wurden.
Wenn Sie sich C# 1.0 jedoch heute ansehen, wird Ihnen schwindlig. Es fehlten die integrierten Async-Funktionen
und einige der cleveren Funktionen bezüglich Generics, die heute als selbstverständlich betrachtet werden. In
der Tat fehlten Generics vollständig. Und was ist mit LINQ? War noch nicht verfügbar. Bis zum Erscheinen dieser
Erweiterungen dauerte es noch einige Jahre.
Im Vergleich zu heute sieht C# Version 1.0 jedoch ziemlich minimalistisch aus. Man musste selbst ausführlichen
Code schreiben. Irgendwo musste man jedoch anfangen. C# Version 1.0 war eine brauchbare Alternative zu Java
auf der Windows-Plattform.
Die wichtigsten Features von C# 1.0 umfassten:
Klassen
Strukturen
Schnittstellen
Ereignisse
Eigenschaften
Delegaten
Operatoren und Ausdrücke
Anweisungen
Attribute
C# Version 1.2
C# Version 1.2 wird in Visual Studio. NET 2003 bereitgestellt. Sie enthielt einige kleine Verbesserungen der
Sprache. Die wichtigste Änderung betrifft, ab dieser Version, den in einer foreach -Schleife namens Dispose
generierten Code in IEnumerator, wenn IEnumeratorIDisposable implementiert hat.
C# Version 2.0
Nun wird es langsam interessant. Werfen wir einen Blick auf einige wichtige Features von C# 2.0, die im Jahr
2005 zusammen mit Visual Studio 2005 veröffentlicht wurden:
Generics
Partial types (Partielle Typen)
Anonyme Methoden
Auf NULL festlegbare Werttypen
Iteratoren
Kovarianz und Kontravarianz
Andere Features von C# 2.0 fügten vorhandenen Features Funktionen hinzu:
Separate Getter- und Setter-Zugriffsfunktionen
Konvertierung von Methodengruppen (Delegate)
Statische Klassen
Delegatrückschlüsse
C# mag als allgemein gehaltene, objektorientierte (OO) Sprache angefangen haben. Das änderte sich mit C#
Version 2.0 jedoch schnell. Sobald man diese im Griff hatte, widmete man sich einigen ernsten Problemfeldern
der Entwickler. Und zwar in bedeutungsvoller Art und Weise.
Mit Generics können Typen und Methoden für einen beliebigen Typ ausgeführt werden, und sie behalten
dennoch ihre Typsicherheit bei. Wenn Sie zum Beispiel List<T> haben, können Sie List<string> oder
List<int> verwenden und typsichere Vorgänge für diese Zeichenfolgen oder Ganzzahlen ausführen, während
Sie sie durchlaufen. Die Verwendung von Generics ist besser als das Erstellen eines ListInt -Typs, der für jeden
Vorgang von ArrayList abgeleitet oder aus Object umgewandelt wird.
C# Version 2.0 brachte Iteratoren mit sich. Kurz gesagt können Sie mit Iteratoren alle Elemente in einem List
(oder anderen Enumerable-Typen) mit einer foreach -Schleife untersuchen. Als erstklassiger Bestandteil der
Sprache verbesserten Iteratoren die Lesbarkeit der Sprache und die Möglichkeiten zum Erörtern des Codes
erheblich.
Trotzdem hinkte C# weiter ein wenig hinter Java her. Von Java gab es bereits Versionen mit Generics und
Iteratoren. Das sollte sich jedoch bald ändern, da sich die Sprachen weiter voneinander weg entwickelten.
C# Version 3.0
C# Version 3.0 wurde Ende 2007 zusammen mit Visual Studio 2008 veröffentlicht. Der volle Umfang an
Sprachfeatures kam tatsächlich jedoch erst mit .NET Framework Version 3.5. Diese Version markierte eine
wesentliche Veränderung in der Entwicklung von C#. Sie etablierte C# als eine wirklich beachtliche
Programmiersprache. Werfen wir einen Blick auf einige wichtige Features in dieser Version:
Automatisch implementierte Eigenschaften
Anonyme Typen
Query expressions (Abfrageausdrücke)
Lambda-Ausdrücke
Ausdrucksbaumstrukturen
Erweiterungsmethoden
Implizit typisierte lokale Variablen
Partielle Methoden
Objekt- und Elementinitialisierer
Rückblickend scheinen viele dieser Features unvermeidlich und untrennbar. Sie alle passen strategisch
zusammen. Im Allgemeinen wird angenommen, dass das durchschlagende Feature dieser Version von C# der
Abfrageausdruck war, auch bekannt als Language Integrated Query (LINQ).
Bei genauerem Hinsehen zeigt sich, dass Ausdrucksbaumstrukturen, Lambdaausdrücke und anonyme Typen die
Grundlage waren, auf der LINQ konstruiert wurde. In jedem Fall stellte C# 3.0 ein revolutionäres Konzept dar. C#
3.0 hatte mit dem Schaffen der Grundlagen begonnen, um C# in eine hybride objektorientierte/funktionale
Sprache zu verwandeln.
Konkret konnte man nun deklarative Abfragen im Stil von SQL schreiben, unter anderem um Vorgänge an
Sammlungen durchzuführen. Anstatt eine for -Schleife zu schreiben, um den Durchschnitt einer Liste von
ganzen Zahlen zu berechnen, konnte man dies nun einfach als list.Average() ausführen. Die Kombination aus
Abfrageausdrücken und Erweiterungsmethoden ließ es jedoch so aussehen, als wäre die Liste der ganzen
Zahlen sehr viel intelligenter geworden.
Es dauerte eine Weile, aber nach und nach verstanden die Menschen das Konzept wirklich und integrierten es.
Und nun, Jahre später, ist der Code sehr viel präziser, einfacher und funktionaler.
C# Version 4.0
Die C#-Version 4.0, die mit Visual Studio 2010 veröffentlicht wurde, konnte nicht an den Erfolg der Version 3.0
anknüpfen. Mit Version 3.0 bewegte sich C# deutlich aus dem Schatten von Java heraus und gewann an
Bedeutung. Die Sprache wurde schnell elegant.
Die nächste Version führte einige interessante neue Features ein:
Dynamische Bindung
Benannte/optionale Argumente
Generische Kovarianz und Kontravarianz
Eingebettete Interop-Typen
Durch eingebettete Interop-Typen wird die Bereitstellung von COM-Interopassemblys für Ihre Anwendung
vereinfacht. Generische Kovarianz und Kontravarianz bieten Ihnen mehr Möglichkeiten zum Verwenden von
Generics. Sie sind allerdings recht theoretisch und werden vermutlich von Framework- und Bibliotheksautoren
am meisten geschätzt. Mit benannten und optionalen Parametern können Sie Methodenüberladungen
eliminieren. Außerdem bieten sie Bequemlichkeit. Keines dieser Features war jedoch bahnbrechend.
Das wichtigste Feature war die Einführung des dynamic -Schlüsselworts. Das in C# Version 4.0 eingeführte
dynamic -Schlüsselwort gibt die Möglichkeit zum Überschreiben des Compilers bei Eingabe zur Kompilierzeit.
Durch die Verwendung des dynamischen Schlüsselworts können Sie Konstrukte schreiben, die dynamisch
typisierten Sprachen wie JavaScript ähneln. Sie können ein dynamic x = "a string" erstellen und dann sechs
hinzufügen, und überlassen Sie es der Runtime herauszufinden, was als Nächstes geschehen soll.
Die dynamische Bindung kann zu Fehlern führen, bietet aber gleichzeitig eine hohe Leistungsfähigkeit innerhalb
der Sprache.
C# Version 5.0
In der C#-Version 5.0, die mit Visual Studio 2012 veröffentlicht wurde, lag der Fokus auf ganz bestimmten
Sprachaspekten. Nahezu die gesamte Arbeit für diese Version war einem weiteren bahnbrechenden
Sprachkonzept gewidmet: den Modellen async und await für die asynchrone Programmierung. Hier ist die
Liste der wichtigsten Features:
Asynchrone Member
Attribute „CallerInfo“
Siehe auch
Code Project: Caller Info Attributes in C# 5.0 (Aufruferinformationsattribute in C# 5.0)
Mit dem Attribut „CallerInfo“ können Sie leicht Informationen über den Kontext erhalten, in dem Sie ausführen,
ohne auf Dutzende Reflektionscodebausteine zurückzugreifen. Es gibt viele Verwendungsmöglichkeiten für
Diagnose- und Protokollierungsaufgaben.
Die eigentlichen Stars dieser Version sind aber async und await . Als diese Features im Jahr 2012
herauskamen, veränderte C# noch einmal alles, indem Asynchronität als erstrangiger Bestandteil in die Sprache
eingearbeitet wurde. Wenn Sie schon einmal mit langlaufenden Vorgängen und der Implementierung von
Rückrufnetzen zu tun hatten, haben Sie dieses Sprachfeature vermutlich zu schätzen gelernt.
C# Version 6.0
Mit den Versionen 3.0 und 5.0 wurde C# um wichtige neue Features in einer objektorientierten Sprache
erweitert. In Version 6.0, die mit Visual Studio 2015 veröffentlicht wurde, lag der Fokus nicht auf einem
einzelnen Hauptfeature, sondern auf der Implementierung vieler kleiner Verbesserungen, die das
Programmieren mit C# noch effizienter machten. Hier sind einige davon:
Statische Importe
Ausnahmefilter
Initialisierer für automatische Eigenschaften
Ausdruckskörpermember
Nullpropagator
Zeichenfolgeninterpolation
nameof-Operator
Zu weiteren neuen Features gehören:
Indexinitialisierer
„Await“ in Catch- und Finally-Blöcken
Standardwerte für Getter-exklusive Eigenschaften
Jedes dieser Features ist auf seine eigene Weise interessant. Wenn Sie die Features jedoch zusammen
betrachten, erkennen Sie ein interessantes Muster. In dieser Version von C# wurden Codebausteine eliminiert,
um den Code knapper und besser lesbar zu machen. Für Anhänger von klarem, einfachem Code war diese
Sprachversion ein großer Gewinn.
Neben dieser Version wurde noch etwas anderes gemacht, auch wenn es sich nicht um ein herkömmliches
Sprachfeature handelt. Der Compiler Roslyn wurde als Dienst veröffentlicht. Der C#-Compiler ist nun in C#
geschrieben, und Sie können den Compiler als Teil Ihrer Programmierarbeiten verwenden.
C# Version 7.0
Die C#-Version 7.0 wurde mit Visual Studio 2017 veröffentlicht. Diese Version bietet einige evolutionäre und
tolle Aspekte im Stil von C# 6.0, aber ohne den Compiler als Dienst. Hier sind einige der neuen Features:
Out-Variablen
Tupel und Dekonstruktionen
Mustervergleich
Local functions (Lokale Funktionen)
Erweiterte Ausdruckskörpermember
Lokale ref-Variablen und Rückgaben
Weitere Features umfassten:
Verwerfen
Binäre Literale und Zahlentrennzeichen
Throw expressions (Throw-Ausdrücke)
Alle diese Features bieten tolle neue Möglichkeiten für Entwickler und erlauben es, einen noch saubereren Code
als je zuvor zu schreiben. Ein Highlight ist das Verdichten der Variablendeklaration zur Verwendung mit dem
out -Schlüsselwort und indem die Rückgabe mehrerer Werte über Tupel erlaubt wird.
C# kommt auf einem immer breiteren Feld zum Einsatz. .NET Core zielt nun auf alle Betriebssysteme ab und
konzentriert sich stark auf die Cloud und auf Portabilität. Diese neuen Funktionen nehmen zusätzlich zum
Entwickeln neuer Features sicherlich viel Arbeit und Zeit in Anspruch.
C#-Version 7.1
Die Punktreleases von C# wurden mit C# 7.1 eingeführt. Mit dieser Version wurde das Konfigurationselement
zur Auswahl der Sprachversion, drei neue Sprachfeatures und neues Compilerverhalten hinzugefügt.
Die neuen Sprachfeatures in diesem Release umfassen:
async Main -Methode
Der Einstiegspunkt für eine Anwendung kann über den Modifizierer async verfügen.
default Literale Ausdrücke
Sie können literale Standardausdrücke in Standardwertausdrücken verwenden, wenn der Zieltyp
abgeleitet werden kann.
Abgeleitete Tupelelementnamen
Die Namen von Tupelelementen können in den meisten Fällen von der Initialisierung eines Tupels
abgeleitet werden.
Musterabgleich für generische Typparameter
Sie können Musterabgleichsausdrücke für Variablen verwenden, deren Typ ein generischer
Typparameter ist.
Außerdem verfügt der Compiler über die zwei Optionen -refout und -refonly , mit denen die Generierung der
Referenzassembly gesteuert wird.
C#-Version 7.2
Mit C# 7.2 wurden einige kleine Sprachfeatures hinzugefügt:
Techniken zum Schreiben von sicherem, effizientem Code
Eine Kombination aus Verbesserungen der Syntax, die das Arbeiten mit Werttypen mithilfe von
Verweissemantik ermöglichen.
Nicht schließende benannte Argumente
Positionelle Argumente können auf benannte Argumente folgen.
Führende Unterstriche in numerischen Literalen
Numerische Literale dürfen jetzt führende Unterstriche vor aufgeführten Stellen aufweisen.
private protected -Zugriffsmodifizierer
Der private protected -Zugriffsmodifizierer ermöglicht den Zugriff für abgeleitete Klassen innerhalb
der gleichen Assembly.
Bedingte ref Ausdrücke
Das Ergebnis eines bedingten Ausdrucks ( ?: ) kann jetzt ein Verweis sein.
C#-Version 7.3
Es gibt zwei Hauptdesigns in der C# 7.3-Version. Ein Design bietet Features, die ermöglichen, dass sicherer Code
so leistungsfähig ist wie unsicherer Code. Das zweite Design bietet inkrementelle Verbesserungen vorhandener
Funktionen. Darüber hinaus wurden in diesem Release neue Compileroptionen hinzugefügt.
Die folgenden neuen Features unterstützen das Design der besseren Leistung für sicheren Code:
Sie können ohne Anpinnen auf fixierte Felder zugreifen.
Sie können lokale ref -Variablen neu zuweisen.
Sie können Initialisierer für stackalloc -Arrays verwenden.
Sie können fixed -Anweisungen mit jedem Typ verwenden, der ein Muster unterstützt.
Sie können generischere Einschränkungen verwenden.
Die folgenden Verbesserungen wurden an vorhandenen Features vorgenommen:
Sie können == und != mit Tupeltypen testen.
Sie können Ausdrucksvariablen an mehreren Standorten verwenden.
Sie können Attribute dem Unterstützungsfeld automatisch implementierter Eigenschaften anfügen.
Die Auflösung der Methode, wenn Argumente um in differieren, wurde verbessert.
Die Auflösung von Überladungen weist jetzt weniger mehrdeutige Fälle auf.
Die neuen Compileroptionen lauten:
-publicsign , um das Signieren von Assemblys durch Open Source Software (OSS) zu ermöglichen.
-pathmap , um eine Zuordnung für Quellverzeichnisse bereitzustellen.
C#-Version 8.0
C# 8.0 ist das erste Hauptrelease von C#, das speziell auf .NET Core ausgerichtet ist. Einige Features nutzen neue
CLR-Funktionen, während andere Bibliothekstypen nutzen, die nur in .NET Core hinzugefügt wurden. C# 8.0 fügt
der Sprache C# die folgenden Features und Verbesserungen hinzu:
Readonly-Member
Standardschnittstellenmethoden
Verbesserungen am Musterabgleich:
switch-Ausdrücke
Eigenschaftsmuster
Tupelmuster
Positionsmuster
using-Deklarationen
Statische lokale Funktionen
Verwerfbare Referenzstrukturen
Nullwerte zulassende Verweistypen
Asynchrone Streams
Indizes und Bereiche
NULL-Coalescing-Zuweisung
Nicht verwaltete konstruierte Typen
Stackalloc in geschachtelten Ausdrücken
Erweiterung von interpolierten ausführlichen Zeichenfolgen
Standardschnittstellenmember erfordern Verbesserungen in der CLR (Common Language Runtime). Diese
Features wurden in der CLR für .NET Core 3.0 hinzugefügt. Bereiche, Indizes und asynchrone Datenströme
erfordern neue Typen in den .NET Core 3.0-Bibliotheken. Nullable-Verweistypen, die im Compiler implementiert
sind, sind weitaus nützlicher, wenn Bibliotheken mit Anmerkungen versehen sind, um semantische
Informationen zum NULL-Status von Argumenten und Rückgabewerten anzugeben. Diese Anmerkungen
werden in den .NET Core-Bibliotheken hinzugefügt.
C#-Version 9.0
C# 9.0 wurde mit .NET 5 veröffentlicht. Es ist die Standardsprachversion für jede Assembly, die auf das .NET 5-
Release ausgelegt ist. Es enthält die folgenden neuen und verbesserten Features:
Mit Version 9.0 wird die Sprache C# um die folgenden Features und Verbesserungen erweitert:
Datensätze
init-only-Setter
Top-Level-Anweisungen
Verbesserungen am Musterabgleich:
Leistung und Interop
Integerwerte mit nativer Größe
Funktionszeiger
Unterdrücken der Ausgabe des Flags „localsinit“
Anpassen und Fertigstellen von Features
Zieltypisierte new -Ausdrücke
Anonyme static -Funktionen
Bedingter Ausdruck mit Zieltyp
Kovariante Rückgabetypen
Unterstützung für die Erweiterung GetEnumerator für foreach -Schleifen
Parameter zum Verwerfen von Lambdafunktion
Attribute in lokalen Funktionen
Unterstützung für Code-Generatoren
Modulinitialisierer
Neue Features für partielle Methoden
C# 9.0 setzt drei der Themen aus früheren Versionen fort: Entfernen von Aufwand, Trennen von Daten und
Algorithmen sowie Bereitstellen von mehr Mustern an mehr Stellen.
Anweisungen der obersten Ebene bedeuten, dass das Hauptprogramm einfacher zu lesen ist. Es besteht weniger
Aufwand: ein Namespace, die Program -Klasse und static void Main() sind nicht erforderlich.
Die Einführung von records ermöglicht eine kompakte Syntax für Verweistypen, die der Wertsemantik für
Gleichheit folgen. Mit diesen Typen definieren Sie Datencontainer, die typischerweise ein Mindestverhalten
festlegen. init-only-Setter ermöglichen nicht destruktive Mutationen with (Ausdrücke) in Datensätzen. C# 9.0
fügt auch kovariante Rückgabetypen hinzu, sodass abgeleitete Datensätze virtuelle Methoden überschreiben
und einen Typ zurückgeben können, der vom Rückgabetyp der Basismethode abgeleitet ist.
Die Möglichkeiten zum Musterabgleich wurden auf verschiedene Weisen erweitert. Numerische Typen
unterstützen jetzt Bereichsmuster. Muster können mit den Mustern and , or und not kombiniert werden.
Klammern können hinzugefügt werden, um komplexere Muster zu verdeutlichen.
Eine weitere Reihe von Features unterstützt High Performance Computing in C#:
Die Typen nint und nuint modellieren die Integertypen mit nativer Größe auf der Ziel-CPU.
Funktionszeiger stellen delegatähnliche Funktionen zur Verfügung und vermeiden gleichzeitig die
Zuteilungen, die zum Erstellen eines Delegatobjekts erforderlich sind.
Die Anweisung localsinit kann zum Speichern von Anweisungen weggelassen werden.
Eine weitere Reihe von Verbesserungen gilt für Szenarien, in denen Codegeneratoren Funktionalität hinzufügen:
Modulinitialisierer sind Methoden, die die Laufzeit beim Laden einer Assembly aufruft.
Partielle Methoden unterstützen neue zugängliche Modifizierer und Rückgabetypen des Typs „nicht-void“. In
diesen Fällen muss eine Implementierung bereitgestellt werden.
C# 9.0 wurden viele weitere kleine Features hinzugefügt, die die Produktivität von Entwicklern verbessern,
sowohl beim Schreiben als auch beim Lesen von Code:
new -Ausdrücke mit Zieltyp
Anonyme static -Funktionen
Bedingte Ausdrücke mit Zieltyp
Unterstützung für die Erweiterung GetEnumerator() in foreach -Schleifen
Lambdaausdrücke können das Verwerfen von Parametern deklarieren
Attribute können auf lokale Funktionen angewendet werden
Die C#-Version 9.0 ist eine Weiterentwicklung von C# als moderne, universell einsetzbare Programmiersprache.
Features unterstützen weiterhin moderne Workloads und Anwendungstypen.
Dieser Artikel wurde ursprünglich auf dem NDepend-Blog veröffentlicht und uns freundlicherweise von Erik
Dietrich und Patrick Smacchia zur Verfügung gestellt.
Die Beziehung zwischen Sprachfeatures und
Bibliothekstypen
04.11.2021 • 2 minutes to read
Die C#-Sprachdefinition erfordert eine Standardbibliothek, damit bestimmte Typen und bestimmte zugängliche
Members für diese Typen verfügbar sind. Der Compiler generiert Code, der die erforderlichen Typen und
Members für viele verschiedene Sprachfeatures verwendet. Beim Schreiben von Code für Umgebungen, für die
diese Typen und Members noch nicht bereitgestellt wurden, stehen bei Bedarf NuGet-Pakete zur Verfügung, die
Typen enthalten, die für neuere Versionen der Sprache erforderlich sind.
Diese Abhängigkeit von den Funktionen der Standardbibliothek ist seit der ersten Version Teil der Sprache „C#“.
In dieser Version sind folgende Beispiele enthalten:
Exception: wird für alle vom Compiler generierten Ausnahmen verwendet.
String: Der C#-Typ string stellt ein Synonym für String dar.
Int32: Synonym von int .
Diese erste Version war einfach, denn der Compiler und die Standardbibliothek waren beide enthalten, und von
beiden gab es nur eine Version.
Bei nachfolgenden Versionen von C# wurden den Abhängigkeiten gelegentlich neue Typen oder Members
hinzugefügt. Beispiele dafür sind INotifyCompletion, CallerFilePathAttribute und CallerMemberNameAttribute.
In C# 7.0 wird dies fortgesetzt, indem die Abhängigkeit von ValueTuple hinzugefügt wird, um das Sprachfeature
für Tupels hinzuzufügen.
Das Entwurfsteam für die Sprache arbeitet daran, die Oberfläche der Typen und Members zu verringern, die für
eine kompatible Standardbibliothek erforderlich sind. Das Ziel ist ein übersichtliches Design, bei dem neue
Bibliotheksfeatures nahtlos in die Sprache integriert werden können. In zukünftigen Versionen von C# werden
neue Features hinzugefügt, die neue Typen und Members in einer Standardbibliothek erfordern. Es ist wichtig,
ein Verständnis dafür zu entwickeln, wie Sie diese Abhängigkeiten in Ihrer Arbeit verwalten.
Kompatibilität ist ein sehr wichtiges Ziel, wenn der Sprache C# neue Features hinzugefügt werden. In nahezu
allen Fällen kann vorhandener Code problemlos mit einer neuen Compilerversion neu kompiliert werden.
Möglicherweise ist mehr Sorgfalt erforderlich, wenn Sie neue Sprachfeatures in einer Bibliothek übernehmen.
Sie erstellen möglicherweise eine neue Bibliothek mit Features, die sich in der neuesten Version befinden, und
müssen sicherstellen, dass Apps, die mit früheren Versionen des Compilers erstellt wurden, diese verwenden
können. Oder Sie nehmen ein Upgrade einer vorhandenen Bibliothek vor, und viele Ihrer Benutzer verfügen
möglicherweise noch nicht über Versionen mit dem Upgrade. Beim Treffen von Entscheidungen zur Übernahme
neuer Features müssen Sie zwei Varianten von Kompatibilität unterscheiden: quellkompatibel und
binärkompatibel.
Binärkompatible Änderungen
Änderungen an Ihrer Bibliothek sind binärkompatibel , wenn Ihre aktualisierte Bibliothek ohne Neuerstellung
der Anwendungen und Bibliotheken, die Sie nutzen, verwendet werden kann. Abhängige Assemblys müssen
nicht neu erstellt werden, und es sind keine Änderungen am Quellcode erforderlich.
Quellkompatible Änderungen
Änderungen an Ihrer Bibliothek sind quellkompatibel , wenn die Anwendungen und Bibliotheken, die Ihre
Bibliothek nutzen, keine Änderungen am Quellcode erfordern, die Quellen aber mit der neuen Version neu
kompiliert werden müssen, um ordnungsgemäß zu funktionieren.
Inkompatible Änderungen
Wenn eine Änderung weder quellkompatibel noch binärkompatibel ist, sind Änderungen am Quellcode in
Kombination mit einer erneuten Kompilierung in den abhängigen Bibliotheken und Anwendungen erforderlich.
Neuer Code:
public double CalculateSquare(double value) => value * value;
Mit quellkompatiblen Änderungen wird Syntax eingeführt, die den kompilierten Code für ein öffentliches
Member ändert, aber in einer Weise, die mit vorhandenen Aufrufsites kompatibel ist. Beispielsweise ist das
Ändern einer Methodensignatur von einem Wertparameter in einen in -Verweisparameter quellkompatibel,
aber nicht binärkompatibel:
Ursprünglicher Code:
Neuer Code:
In den Neuigkeiten-Artikeln ist vermerkt, ob die Einführung eines Features, das sich auf öffentliche
Deklarationen auswirkt, quellkompatibel oder binärkompatibel ist.
Erstellen von Datensatztypen
04.11.2021 • 12 minutes to read
Mit C# 9 wurden Datensätze eingeführt. Dabei handelt es sich um einen neuen Verweistyp, den Sie anstelle von
Klassen oder Strukturen erstellen können. C# 10 führt Datensatzstrukturen ein, mit denen Sie Datensätze als
Werttypen definieren können. Datensätze unterscheiden sich insofern von Klassen, dass Datensatztypen
wertbasierte Gleichheit verwenden. Zwei Variablen eines Datensatztyps sind gleich, wenn die
Datensatztypdefinitionen identisch sind und wenn die Werte in beiden Datensätzen für alle Felder identisch sind.
Zwei Variablen eines Klassentyps sind identisch, wenn die Objekte, auf die verwiesen wird, denselben Klassentyp
aufweisen und die Variablen auf dasselbe Objekt verweisen. Die wertbasierte Gleichheit impliziert andere
Funktionen, die Sie wahrscheinlich in Datensatztypen benötigen. Der Compiler generiert viele dieser Member,
wenn Sie record anstelle von class deklarieren. Der Compiler generiert dieselben Methoden für
record struct -Typen.
Voraussetzungen
Sie müssen Ihren Computer für die Ausführung von .NET 6 oder höher einrichten, einschließlich des Compilers
für C# 10.0 oder höher. Der C# 9.0-Compiler steht ab der Vorschauversion von Visual Studio 2022 oder ab dem
.NET 6.0 SDK zur Verfügung.
Mit dem obigen Code wird ein Datensatz mit fester Breite definiert. Der DailyTemperature -Datensatz gehört
zum Typ readonly record struct , da er nicht vererben und unveränderlich sein sollte. Bei den Eigenschaften
HighTemp und LowTemp handelt es sich um init-only-Eigenschaften, d. h., sie können im Konstruktor oder
mithilfe eines Eigenschafteninitialisierers festgelegt werden. Wenn Lese-/Schreibzugriff auf positionale
Parameter bestehen soll, müssen Sie record struct anstelle von readonly record struct deklarieren. Der Typ
DailyTemperature verfügt ebenfalls über einen primären Konstruktor , der über zwei Parameter verfügt, die den
zwei Eigenschaften entsprechen. Sie verwenden den primären Konstruktor zum Initialisieren eines
DailyTemperature -Datensatzes:
private static DailyTemperature[] data = new DailyTemperature[]
{
new DailyTemperature(HighTemp: 57, LowTemp: 30),
new DailyTemperature(60, 35),
new DailyTemperature(63, 33),
new DailyTemperature(68, 29),
new DailyTemperature(72, 47),
new DailyTemperature(75, 55),
new DailyTemperature(77, 55),
new DailyTemperature(72, 58),
new DailyTemperature(70, 47),
new DailyTemperature(77, 59),
new DailyTemperature(85, 65),
new DailyTemperature(87, 65),
new DailyTemperature(85, 72),
new DailyTemperature(83, 68),
new DailyTemperature(77, 65),
new DailyTemperature(72, 58),
new DailyTemperature(77, 55),
new DailyTemperature(76, 53),
new DailyTemperature(80, 60),
new DailyTemperature(85, 66)
};
Sie können Ihre eigenen Eigenschaften oder Methoden zu Datensätzen hinzufügen, dazu zählen auch Datensätze
mit fester Breite. Sie müssen die Durchschnittstemperatur für jeden Tag berechnen. Diese Eigenschaft können
Sie zum Datensatz DailyTemperature hinzufügen:
Als Nächstes stellen Sie sicher, dass Sie diese Daten verwenden können. Fügen Sie der Main -Methode den
folgenden Code hinzu:
Führen Sie Ihre Anwendung aus. Daraufhin sollte Ihnen eine Ausgabe angezeigt werden, die der folgenden
Anzeige ähnelt (einige Zeilen wurden aus Platzgründen entfernt):
Der obige Code zeigt die Ausgabe der Überschreibung von ToString an, die vom Compiler synthetisiert wurde.
Wenn Sie einen anderen Text bevorzugen, können Sie Ihre eigene Version von ToString schreiben, die den
Compiler daran hindert, eine Version für Sie zu synthetisieren.
Der abstrakte Datensatz DegreeDays ist die gemeinsame Basisklasse für die beiden Datensätze
HeatingDegreeDays und CoolingDegreeDays . Die primären Konstruktordeklarationen der abgeleiteten Datensätze
zeigen, wie die Initialisierung des Basisdatensatzes verwaltet wird. Ihr abgeleiteter Datensatz deklariert
Parameter für alle Parameter im primären Konstruktor des Basisdatensatzes. Der Basisdatensatz deklariert und
initialisiert diese Eigenschaften. Der abgeleitete Datensatz blendet diese nicht aus. Stattdessen erstellt und
initialisiert er nur Eigenschaften für Parameter, die nicht in seinem Basisdatensatz deklariert sind. In diesem
Beispiel fügen die abgeleiteten Datensätze keine neuen primären Konstruktorparameter hinzu. Testen Sie Ihren
Code, indem Sie den folgenden Code zu Ihrer Main -Methode hinzufügen:
Diese Regeln sind am einfachsten zu verstehen, wenn Sie den Zweck von PrintMembers verstehen.
PrintMembers fügt Informationen zu jeder Eigenschaft in einem Datensatztyp zu einer Zeichenfolge hinzu. Der
Vertrag erfordert, dass Basisdatensätze ihre Member zur Anzeige hinzufügen, und geht davon aus, dass
abgeleitete Member ihre Member hinzufügen. Jeder Datensatztyp synthetisiert eine ToString -Überschreibung,
die dem folgenden Beispiel für HeatingDegreeDays ähnelt:
Sie deklarieren eine PrintMembers -Methode im Datensatz DegreeDays , der den Typ der Sammlung nicht ausgibt:
Die Signatur deklariert eine virtual protected -Methode entsprechend der Version des Compilers. Sie müssen
sich keine Sorgen darüber machen, dass Sie die falschen Zugriffsmethoden verwenden, da die Sprache die
richtige Signatur erzwingt. Wenn Sie die richtigen Modifizierer für synthetisierte Methoden vergessen, gibt der
Compiler Warnungen oder Fehler aus, die Sie dabei unterstützen, die richtige Signatur zu verwenden.
Ab C# 10.0 können Sie die ToString -Methode im Datensatztyp als sealed deklarieren. Dadurch wird
verhindert, dass abgeleitete Datensätze eine neue Implementierung bereitstellen. Abgeleitete Datensätze
enthalten weiterhin die Überschreibung PrintMembers . Die ToString -Methode sollte versiegelt werden, wenn
der Laufzeittyp des Datensatzes nicht angezeigt werden soll. Im vorherigen Beispiel würde dabei die Information
verloren gehen, wo der Datensatz Wärme- oder Kältesummen gemessen hat.
Nicht destruktive Änderungen
Die synthetisierten Member in einer positionalen Datensatzklasse ändern den Zustand des Datensatzes nicht.
Das Ziel besteht darin, dass Sie unveränderliche Datensätze einfacher erstellen können. Denken Sie daran, dass
Sie ein readonly record struct -Element deklarieren, um eine unveränderliche Datensatzstruktur zu erstellen.
Sehen Sie sich noch einmal die vorherigen Deklarationen für HeatingDegreeDays und CoolingDegreeDays an. Die
hinzugefügten Member führen Berechnungen der Werte für den Datensatz durch, ändern aber nicht den
Zustand. Datensätze mit fester Breite vereinfachen das Erstellen unveränderlicher Verweistypen.
Das Erstellen unveränderlicher Verweistypen impliziert, dass Sie nicht destruktive Änderungen verwenden
sollten. Sie erstellen neue Datensatzinstanzen mit with -Ausdrücken, die den vorhandenen Datensatzinstanzen
ähneln. Diese Ausdrücke sind eine Kopierkonstruktion mit zusätzlichen Zuweisungen, die die Kopie ändern. Das
Ergebnis ist eine neue Datensatzinstanz, bei der alle Eigenschaften aus dem vorhandenen Datensatz kopiert und
optional geändert wurden. Der ursprüngliche Datensatz bleibt unverändert.
Als Nächstes fügen Sie zur Veranschaulichung von with -Ausdrücken einige Features zu Ihrem Programm
hinzu. Zuerst erstellen Sie einen neuen Datensatz zum Berechnen steigender Wärmesummen mithilfe derselben
Daten. Für steigende Wärmesummen werden in der Regel 41 F als Baselinetemperatur verwendet und sie
messen Temperaturen über der Baseline. Sie können einen neuen Datensatz erstellen, der coolingDegreeDays
ähnelt, aber eine andere Baselinetemperatur verwendet, um dieselben Daten zu verwenden:
Sie können die Anzahl der berechneten Temperaturen mit den generierten Zahlen mit einer höheren
Baselinetemperatur vergleichen. Denken Sie daran, dass Datensätze Verweistypen sind und dass es sich bei
diesen Kopien um flache Kopien handelt. Das Array für die Daten wird nicht kopiert, aber beide Datensätze
beziehen sich auf dieselben Daten. Dies ist in einem anderen Szenario von Vorteil. Bei steigenden
Wärmesummen ist es nützlich, die Gesamtsumme der letzten fünf Tage zu überwachen. Mithilfe von with -
Ausdrücken können Sie neue Datensätze mit anderen Quelldaten erstellen. Mit dem folgenden Code wird eine
Sammlung dieser Akkumulationen erstellt, und anschließend werden die Werte angezeigt:
Sie können auch with -Ausdrücke verwenden, um Kopien von Datensätzen zu erstellen. Geben Sie keine
Eigenschaften zwischen den geschweiften Klammern für den with -Ausdruck an. Das bedeutet, eine Kopie wird
erstellt und es werden keine Eigenschaften geändert:
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET 6 einrichten, einschließlich des C# 10.0-Compilers. Der
C# 10.0-Compiler steht ab Visual Studio 2022 oder mit dem .NET 6.0 SDK zur Verfügung.
In diesem Tutorial wird vorausgesetzt, dass Sie C# und .NET, einschließlich Visual Studio oder die .NET Core-CLI
kennen.
Ausprobieren
Mithilfe von Anweisungen der obersten Ebene können Sie den zusätzlichen erforderlichen Schritt vermeiden,
indem Sie den Einstiegspunkt Ihres Programms in einer statischen Methode in einer Klasse platzieren. Der
typische Startpunkt für eine neue Konsolenanwendung sieht wie der folgende Code aus:
using System;
namespace Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Der vorangehende Code ist das Ergebnis der Ausführung des dotnet new console -Befehls und der Erstellung
einer neuen Konsolenanwendung. Diese elf Zeilen enthalten nur eine Zeile ausführbaren Codes. Sie können
dieses Programm mit dem neuen Feature für Anweisungen der obersten Ebene vereinfachen. Dadurch können
Sie in diesem Programm bis auf zwei Zeilen alle anderen Zeilen entfernen.
Durch dieses Feature wird das Umsetzen neuer Ideen vereinfacht. Sie können Anweisungen der obersten Ebene
für Skripterstellungsszenarios oder zum Durchsuchen verwenden. Sobald Sie die Grundlagen beherrschen,
können Sie mit dem Umgestalten des Codes beginnen und Methoden, Klassen oder andere Assemblys für von
Ihnen entwickelte wiederverwendbare Komponenten erstellen. Anweisungen der obersten Ebene ermöglichen
das schnelle Testen von Code und bieten eine gute Grundlage für Einsteigertutorials. Außerdem bieten sie eine
praktische Möglichkeit für den Übergang von Experimenten mit Code hin zu vollständigen Programmen.
Anweisungen der obersten Ebene werden in der Reihenfolge ausgeführt, in der sie in der Datei aufgeführt sind.
Sie können nur in einer Quelldatei in der Anwendung verwendet werden. Der Compiler generiert einen Fehler,
wenn Sie die Anweisungen in mehr als einer Datei verwenden.
Console.WriteLine(args);
Sie deklarieren keine args -Variable. Im Zusammenhang mit der einzelnen Quelldatei, die die Anweisungen der
obersten Ebene enthält, erkennt der Compiler, dass args für die Befehlszeilenargumente steht. Dies sind wie in
allen C#-Programmen Argumente vom Typ string[] .
Sie können den Code testen, indem Sie den folgenden dotnet run -Befehl ausführen:
Die Argumente nach -- in der Befehlszeile werden an das Programm weitergegeben. Sie können den Typ der
args -Variable sehen, da diese Information in der Konsole angezeigt wird:
System.String[]
Sie müssen die Argumente einzeln benennen und durch ein Leerzeichen trennen, um die Frage in die Konsole zu
schreiben. Ersetzen Sie den WriteLine -Aufruf durch den folgenden Code:
Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();
Wenn Sie das Programm ausführen, wird die Frage nun ordnungsgemäß als Zeichenfolge von Argumenten
angezeigt.
string[] answers =
{
"It is certain.", "Reply hazy, try again.", "Don’t count on it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
};
Dieses Array umfasst zehn zustimmende, fünf zurückhaltende und fünf verneinende Antworten. Fügen Sie als
Nächstes den folgenden Code hinzu, um eine zufällige Antwort aus dem Array zu generieren und anzuzeigen:
Sie können die Anwendung noch mal ausführen, um die Ergebnisse anzuzeigen. Es sollte nun etwa die folgende
Ausgabe angezeigt werden:
Mit diesem Code werden die Fragen beantwortet, allerdings fügen Sie noch ein weiteres Feature hinzu. Ihre
Frage-App soll das Nachdenken vor dem Ausgeben der Antwort simulieren. Dies erreichen Sie, indem Sie eine
ASCII-Animation (American Standard Code for Information Interchange) hinzufügen und den restlichen Vorgang
während der Bearbeitung anhalten. Fügen Sie den folgenden Code nach der Zeile hinzu, die die Frage
wiederholt:
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
Außerdem müssen Sie am Anfang der Quelldatei eine using -Anweisung hinzufügen:
using System.Threading.Tasks;
Die using -Anweisungen müssen sich vor allen anderen Anweisungen in der Datei befinden. Andernfalls tritt ein
Compilerfehler auf. Sie können das Programm noch mal ausführen und die Animation anzeigen. Dies
ermöglicht eine bessere Nutzung des Programms. Experimentieren Sie hinsichtlich der Länge der Verzögerung,
bis Sie mit dem Ergebnis zufrieden sind.
Der vorangehende Code erstellt eine Reihe von Zeilen, die durch ein Leerzeichen voneinander getrennt sind.
Durch das Hinzufügen des Schlüsselworts await wird der Compiler angewiesen, den Programmeinstiegspunkt
als Methode zu generieren, die den async -Modifizierer aufweist und die System.Threading.Tasks.Task-Klasse
zurückgibt. Dieses Programm gibt keinen Wert zurück, sodass der Programmeinstiegspunkt Task zurückgibt.
Wenn das Programm einen ganzzahligen Wert zurückgibt, fügen Sie eine return-Anweisung am Ende der
Anweisungen der obersten Ebene hinzu. Diese return-Anweisung gibt den ganzzahligen Wert an, der
zurückgegeben wird. Wenn die Anweisungen der obersten Ebene einen await -Ausdruck enthalten, wird der
Rückgabetyp zu einer System.Threading.Tasks.Task<TResult>-Klasse.
string[] answers =
{
"It is certain.", "Reply hazy, try again.", "Don't count on it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
};
Der vorangehende Code ist nützlich. Er funktioniert. Er kann jedoch nicht erneut verwendet werden. Nachdem
die Anwendung jetzt funktioniert, ist es an der Zeit, wiederverwendbare Teile zu extrahieren.
Eine Option hierfür ist der Code, der die Animation beim Warten während des Vorgangs anzeigt. Aus diesem
Codeausschnitt kann eine Methode werden:
Erstellen Sie hierfür zunächst eine lokale Funktion in der Datei. Ersetzen Sie die aktuelle Animation durch den
folgenden Code:
await ShowConsoleAnimation();
Der vorangehende Code erstellt eine lokale Funktion in der Main-Methode. Diese ist immer noch nicht
wiederverwendbar. Extrahieren Sie den Code daher in eine Klasse. Erstellen Sie eine neue Datei namens
utilities.cs, und fügen Sie den folgenden Code hinzu:
namespace MyNamespace
{
public static class Utilities
{
public static async Task ShowConsoleAnimation()
{
for (int i = 0; i < 20; i++)
{
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
}
Console.WriteLine();
}
}
}
Eine Datei mit Anweisungen auf oberster Ebene kann auch Namespaces und Typen am Ende der Datei nach den
Anweisungen auf oberster Ebene enthalten. Für dieses Tutorial verwenden Sie die Animationsmethode jedoch in
einer separaten Datei, um sie leichter wiederverwenden zu können.
Zum Schluss können Sie den Animationscode bereinigen, um Duplizierungen zu entfernen:
foreach (string s in new[] { "| -", "/ \\", "- |", "\\ /", })
{
Console.Write(s);
await Task.Delay(50);
Console.Write("\b\b\b");
}
Nun verfügen Sie über eine komplette Anwendung, und Sie haben die wiederverwendbaren Teile für die spätere
Verwendung umgestaltet. Sie können die neue Hilfsprogrammmethode über Ihre Anweisungen der obersten
Ebene abrufen, wie unten in der fertigen Version des Hauptprogramms gezeigt:
using MyNamespace;
Console.WriteLine();
foreach(var s in args)
{
Console.Write(s);
Console.Write(' ');
}
Console.WriteLine();
await Utilities.ShowConsoleAnimation();
string[] answers =
{
"It is certain.", "Reply hazy, try again.", "Don’t count on it.",
"It is decidedly so.", "Ask again later.", "My reply is no.",
"Without a doubt.", "Better not tell you now.", "My sources say no.",
"Yes – definitely.", "Cannot predict now.", "Outlook not so good.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
};
Im Beispiel oben wird der Aufruf Utilities.ShowConsoleAnimation hinzugefügt, und eine zusätzliche using -
Anweisung wird hinzugefügt.
Zusammenfassung
Anweisungen der obersten Ebene vereinfachen das Erstellen einfacher Programme, mit denen neue
Algorithmen untersucht werden können. Sie können mit Algorithmen experimentieren, indem Sie verschiedene
Codeausschnitte ausprobieren. Nachdem Sie gelernt haben, was funktioniert, können Sie den Code so
umgestalten, dass er leichter verwaltet werden kann.
Anweisungen der obersten Ebene vereinfachen Programme, die auf Konsolenanwendungen basieren. Hierzu
gehören Azure-Funktionen, GitHub-Aktionen und andere kleine Hilfsprogramme. Weitere Informationen finden
Sie unter Anweisungen auf oberster Ebene (C#-Programmierleitfaden).
Verwenden des Musterabgleichs zum Gestalten des
Verhaltens von Klassen für besseren Code
04.11.2021 • 10 minutes to read
Die Features für den Musterabgleich in C# stellen die Syntax zum Ausdrücken Ihrer Algorithmen bereit. Mithilfe
dieser Techniken können Sie das Verhalten in Ihren Klassen implementieren. Sie können einen objektorientierten
Klassenentwurf mit einer datenorientierten Implementierung kombinieren, um bei der Modellierung von
Objekten aus der realen Welt präzisen Code bereitzustellen.
In diesem Tutorial lernen Sie Folgendes:
Ausdrücken Ihrer objektorientierten Klassen mithilfe von Datenmustern
Implementieren dieser Muster mithilfe der C#-Features zum Musterabgleich
Nutzen der Compilerdiagnose zum Überprüfen Ihrer Implementierung
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET 5 einrichten, einschließlich des C# 9.0-Compilers. Der
C# 9.0-Compiler steht ab Visual Studio 2019 Version 16.8 Preview oder .NET 5.0 SDK Preview zur Verfügung.
Der vorstehende Code initialisiert das Objekt so, dass beide Tore geschlossen sind und der Wasserstand niedrig
ist. Schreiben Sie als Nächstes den folgenden Testcode in Ihre Main -Methode, um Sie bei der Erstellung einer
ersten Implementierung der Klasse zu leiten:
// Create a new canal lock:
var canalGate = new CanalLock();
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");
Console.WriteLine(canalGate);
canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate: {canalGate}");
canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
Fügen Sie als Nächstes der CanalLock -Klasse eine erste Implementierung jeder Methode hinzu. Der folgende
Code implementiert die Methoden der Klasse, ohne dass die Sicherheitsregeln berücksichtigt werden.
Sicherheitstests fügen Sie später hinzu:
Die Tests, die Sie bisher geschrieben haben, werden bestanden. Sie haben die Grundlagen implementiert.
Schreiben Sie nun einen Test für die erste Fehlerbedingung. Am Ende der vorherigen Tests sind beide Tore
geschlossen, und der Wasserstand ist auf niedrig festgelegt. Fügen Sie einen Test hinzu, um zu versuchen, das
obere Tor zu öffnen:
Console.WriteLine("=============================================");
Console.WriteLine(" Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
canalGate = new CanalLock();
canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation: Can't open the high gate. Water is low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");
Dieser Test schlägt fehl, da das Tor geöffnet wird. Als erste Implementierung können Sie dies mit dem folgendem
Code beheben:
Die Tests werden bestanden. Aber je mehr Tests Sie hinzufügen, desto mehr if -Klauseln werden Sie
hinzufügen und verschiedene Eigenschaften testen. Schon bald werden diese Methoden zu kompliziert, je mehr
Bedingungen Sie hinzufügen.
Versuchen Sie diese Version. Die Tests werden bestanden und der Code bestätigt. Die vollständige Tabelle zeigt
die möglichen Kombinationen von Eingaben und Ergebnissen. Das bedeutet, dass Sie und andere Entwickler
schnell einen Blick auf die Tabelle werfen können, um festzustellen, dass Sie alle möglichen Eingaben abgedeckt
haben. Zur Vereinfachung kann auch der Compiler beitragen. Nachdem Sie den vorherigen Code hinzugefügt
haben, sehen Sie, dass der Compiler eine Warnung generiert: CS8524 bedeutet, dass der Switch-Ausdruck nicht
alle möglichen Eingaben abdeckt. Der Grund für diese Warnung ist, dass eine der Eingaben den Typ enum hat.
Der Compiler interpretiert „alle möglichen Eingaben“ als alle Eingaben des zugrunde liegenden Typs,
typischerweise int . Dieser switch -Ausdruck prüft nur die in enum deklarierten Werte. Um die Warnung zu
entfernen, können Sie für den letzten Teil des Ausdrucks ein Muster des Typs „Alle abfangen und entsorgen“
hinzufügen. Diese Bedingung löst eine Ausnahme aus, da sie eine ungültige Eingabe angibt:
Der vorherige Switch-Arm muss der letzte in Ihrem switch -Ausdruck sein, da er mit allen Eingaben
übereinstimmt. Experimentieren Sie, indem Sie ihn in der Reihenfolge nach vorn verschieben. Dies verursacht
den Compilerfehler CS8510 für nicht erreichbaren Code in einem Muster. Die natürliche Struktur von Switch-
Ausdrücken ermöglicht es dem Compiler, Fehler und Warnungen für mögliche Fehlersituationen zu generieren.
Das „Sicherheitsnetz“ des Compilers erleichtert Ihnen, fehlerfreien Code in weniger Iterationen zu erstellen, und
bietet Ihnen die Möglichkeit, Switch-Arme mit Platzhaltern frei zu kombinieren. Der Compiler löst Fehler aus,
wenn Ihre Kombination zu unerreichbaren Armen führt, mit denen Sie nicht gerechnet haben, und Warnungen,
wenn Sie einen benötigten Arm entfernen.
Die erste Änderung besteht darin, alle Arme zu kombinieren, bei denen der Befehl lautet, das Tor zu schließen.
Das ist stets erlaubt. Fügen Sie den folgenden Code als ersten Arm in den Switch-Ausdruck ein:
Nachdem Sie den vorherigen Switch-Arm hinzugefügt haben, erhalten Sie vier Compilerfehler, einen in jedem
der Arme, in denen der Befehl false ist. Diese Arme sind bereits durch den neu hinzugefügten Arm abgedeckt.
Sie können diese vier Zeilen sicher entfernen. Sie wollten mit diesem neuen Switch-Arm diese Bedingungen
ersetzen.
Als Nächstes können Sie die vier Arme vereinfachen, bei denen der Befehl lautet, das Tor zu öffnen. In beiden
Fällen, in denen der Wasserstand hoch ist, kann das Tor geöffnet werden. (In einem ist es bereits geöffnet.) Ein
Fall, in dem der Wasserstand niedrig ist, löst eine Ausnahme aus, der andere darf nicht passieren. Es sollte sicher
sein, die gleiche Ausnahme auszulösen, wenn sich die Schleuse bereits in einer ungültigen Stellung befindet. Sie
können die folgenden Vereinfachungen für diese Arme vornehmen:
Führen Sie die Tests erneut aus, die bestanden werden. Hier ist die endgültige Version der SetHighGate -
Methode:
Führen Sie Ihre Anwendung erneut aus. Sie erleben, wie die neuen Tests fehlschlagen und die Kanalschleuse in
einen ungültigen Zustand gerät. Versuchen Sie, die restlichen Methoden selbst zu implementieren. Die Methode
zur Festlegung des unteren Tores sollte ähnlich wie die Methode zur Festlegung des oberen Tores sein. Die
Methode zum Ändern des Wasserstands hat unterschiedliche Prüfungen, sollte aber eine ähnliche Struktur
aufweisen. Möglicherweise finden Sie es hilfreich, dasselbe Verfahren auch für die Methode zu verwenden, mit
der der Wasserstand festgelegt wird. Beginnen Sie mit allen vier Eingaben: Stellung der beiden Tore, aktueller
Wasserstand und geforderter neuer Wasserstand. Der Switch-Ausdruck sollte wie folgt beginnen:
Es gibt insgesamt 16 auszufüllende Switch-Arme. Testen und vereinfachen Sie das Ganze im Anschluss.
Haben Sie Methoden in etwa wie folgt gestaltet?
// Change the lower gate.
public void SetLowGate(bool open)
{
LowWaterGateOpen = (open, LowWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.Low) => true,
(true, false, WaterLevel.High) => throw new InvalidOperationException("Cannot open high gate when
the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}
Die Tests sollten bestanden werden, und die Schleuse sollte sicher betrieben werden.
Zusammenfassung
In diesem Tutorial haben Sie gelernt, mithilfe eines Musterabgleichs den internen Zustand eines Objekts zu
überprüfen, ehe Änderungen an diesem Zustand vorgenommen werden. Sie können Kombinationen von
Eigenschaften überprüfen. Sobald Sie Tabellen für beliebige dieser Übergänge erstellt haben, testen Sie Ihren
Code, und vereinfachen ihn dann hinsichtlich Lesbarkeit und Wartbarkeit. Aus diesen anfänglichen Refactorings
können sich weitere Refactorings ergeben, die den internen Zustand validieren oder andere API-Änderungen
verwalten. In diesem Tutorial wurden Klassen und Objekte mit einem eher datenorientierten, musterbasierten
Ansatz kombiniert, um diese Klassen zu implementieren.
Tutorial: Aktualisieren von Schnittstellen mit
Standardschnittstellenmethoden in C# 8.0
04.11.2021 • 5 minutes to read
Ab C# 8.0 können Sie in .NET Core 3.0 eine Implementierung definieren, wenn Sie einen Member einer
Schnittstelle deklarieren. Das häufigste Szenario ist das sichere Hinzufügen von Membern zu einer Schnittstelle,
die bereits veröffentlicht ist und von unzähligen Clients verwendet wird.
In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:
Erweitern Sie Schnittstellen problemlos durch Hinzufügen von Methoden mit Implementierungen.
Erstellen Sie parametrisierte Implementierungen, um größere Flexibilität zu bieten.
Ermöglichen Sie Implementierern, eine spezifischere Implementierung in Form einer Überschreibung zu
bieten.
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten, einschließlich des C# 8.0-Compilers. Der
C# 8.0-Compiler steht ab Visual Studio 2019 Version 16.3 oder mit dem .NET Core 3.0 SDK zur Verfügung.
Von diesen Schnittstellen aus konnte das Team eine Bibliothek für die Benutzer erstellen, um den Kunden eine
bessere Benutzererfahrung zu bieten. Das Ziel bestand darin, eine intensivere Beziehung zu Bestandskunden
aufzubauen und ihre Beziehungen zu neuen Kunden zu verbessern.
Jetzt ist es Zeit, die Bibliothek für das nächste Release zu aktualisieren. Eines der angeforderten Features
gewährt Kunden, die viele Bestellungen aufgeben, einen Treuerabatt. Dieser neue Treuerabatt wird angewendet,
wenn ein Kunde eine Bestellung aufgibt. Der spezifische Rabatt ist eine Eigenschaft jedes einzelnen Kunden. Jede
Implementierung von ICustomer kann andere Regeln für den Treuerabatt festlegen.
Die naheliegendste Methode zum Hinzufügen dieser Funktionalität ist die Verbesserung der ICustomer -
Schnittstelle mit einer Methode zur Anwendung eines Treuerabatts. Diese Entwurfsempfehlung löste bei
erfahrenen Entwicklern Bedenken aus: „Schnittstellen sind unveränderlich, sobald sie veröffentlicht sind! Dies ist
eine einschneidende Änderung!“ C# 8.0 fügt Standardschnittstellenimplementierungen zum Aktualisieren von
Schnittstellen hinzu. Die Autoren der Bibliothek können der Schnittstelle neue Member hinzufügen und eine
Standardimplementierung für diese Member bereitstellen.
Mit Implementierungen von Standardschnittstellen können Entwickler eine Schnittstelle aktualisieren, während
gleichzeitig alle Implementierer diese Implementierung überschreiben können. Benutzer der Bibliothek können
die standardmäßige Implementierung als eine nicht unterbrechende Änderung akzeptieren. Wenn ihre
Geschäftsregeln anders sind, können sie überschreiben.
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
Der Bibliotheksautor schrieb einen ersten Test zum Überprüfen der Implementierung:
Diese Umwandlung von SampleCustomer zu ICustomer ist erforderlich. Die SampleCustomer -Klasse muss keine
Implementierung für ComputeLoyaltyDiscount bereitstellen; dies erfolgt über die ICustomer -Schnittstelle.
Allerdings erbt die SampleCustomer -Klasse keine Member von ihren Schnittstellen. Diese Regel hat sich nicht
geändert. Um jede in der Schnittstelle deklarierte und implementierte Methode aufrufen zu können, muss die
Variable vom Typ der Schnittstelle sein, in diesem Beispiel ICustomer .
// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;
Dieses kleine Codefragment zeigt viele neue Sprachfunktionen. Schnittstellen können nun statische Member
einschließlich Feldern und Methoden enthalten. Verschiedene Zugriffsmodifizierer sind ebenfalls aktiviert. Die
zusätzlichen Felder sind privat, die neue Methode ist öffentlich. Beliebige der Modifizierer sind auf
Schnittstellenmembern erlaubt.
Anwendungen, die die allgemeine Formel zum Berechnen des Treuerabatts verwenden, aber andere Parameter,
müssen keine benutzerdefinierte Implementierung bereitstellen; sie können die Argumente über eine statische
Methode festlegen. Der folgende Code legt z.B. eine „Kundenwertschätzung“ fest, die jeden Kunden mit mehr als
einem Monat Mitgliedschaft belohnt:
In einer Implementierung einer Klasse, die diese Schnittstelle implementiert, kann die Überschreibung die
statische Hilfsmethode aufrufen und diese Logik zum Bereitstellen des „Neuer Kunde“-Rabatts erweitern:
Den vollständigen Code finden Sie in unserem Beispielrepository auf GitHub. Sie erhalten die Startanwendung
von unserem Beispielerepository auf GitHub.
Diese neuen Features bedeuten, dass Schnittstellen problemlos aktualisiert werden können, wenn eine
vernünftige Standardimplementierung für diese neuen Member vorhanden ist. Entwerfen Sie Schnittstellen
sorgfältig, um einzelne funktionale Konzepte auszudrücken, die von mehreren Klassen implementiert werden
können. Dies erleichtert das Aktualisieren dieser Schnittstellendefinitionen, wenn neue Anforderungen für diese
gleichen funktionalen Konzept entdeckt werden.
Tutorial: Untermischen von Funktionalität beim
Erstellen von Klassen mithilfe von Schnittstellen mit
Standardschnittstellenmethoden
04.11.2021 • 8 minutes to read
Ab C# 8.0 können Sie in .NET Core 3.0 eine Implementierung definieren, wenn Sie einen Member einer
Schnittstelle deklarieren. Dieses Feature bietet neue Funktionen, mit denen Sie Standardimplementierungen für
Funktionen definieren können, die in Schnittstellen deklariert werden. Klassen können auswählen, wann die
Funktionalität überschrieben werden soll, wann die Standardfunktionalität verwendet werden soll und wann
keine Unterstützung für diskrete Features deklariert werden soll.
In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:
Erstellen von Schnittstellen mit Implementierungen, die diskrete Features beschreiben.
Erstellen von Klassen, die die Standardimplementierungen verwenden.
Erstellen von Klassen, die einige oder alle Standardimplementierungen überschreiben.
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten, einschließlich des C# 8.0-Compilers. Der
C# 8.0-Compiler steht ab Visual Studio 2019 Version 16.3 oder ab dem .NET Core 3.0 SDK zur Verfügung.
Eine einfache Deckenleuchte könnte diese Schnittstelle wie im folgenden Code dargestellt implementieren:
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
In diesem Tutorial werden keine IoT-Geräte durch den Code gesteuert, sondern diese Aktivitäten werden
emuliert, indem Nachrichten in die Konsole geschrieben werden. Sie können den Code untersuchen, ohne Ihr
Haus zu automatisieren.
Definieren wir nun die Schnittstelle für eine Leuchte, die nach einem Timeout automatisch ausgeschaltet werden
kann:
Sie könnten eine Basisimplementierung zur Deckenleuchte hinzufügen, aber eine bessere Lösung besteht darin,
diese Schnittstellendefinition zu ändern, um eine virtual -Standardimplementierung bereitzustellen:
Durch Hinzufügen dieser Änderung kann die OverheadLight -Klasse die Timerfunktion implementieren, indem
sie Unterstützung für die Schnittstelle deklariert:
Ein anderer Leuchtentyp unterstützt möglicherweise ein anspruchsvolleres Protokoll. Er kann seine eigene
Implementierung für TurnOnFor bereitstellen, wie im folgenden Code gezeigt:
Im Gegensatz zum Überschreiben von Methoden der virtuellen Klasse verwendet die Deklaration von
TurnOnFor in der HalogenLight -Klasse nicht das Schlüsselwort override .
Mix-und-Match-Funktionen
Die Vorteile von Standardschnittstellenmethoden werden deutlicher, wenn Sie erweiterte Funktionen einführen.
Durch die Verwendung von Schnittstellen können Sie Mix-und-Match-Funktionen verwenden. Außerdem kann
jeder Klassenautor zwischen der Standardimplementierung und einer benutzerdefinierten Implementierung
wählen. Fügen wir eine Schnittstelle mit einer Standardimplementierung für eine blinkende Leuchte hinzu:
public interface IBlinkingLight : ILight
{
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Using the default interface method for IBlinkingLight.Blink.");
for (int count = 0; count < repeatCount; count++)
{
SwitchOn();
await Task.Delay(duration);
SwitchOff();
await Task.Delay(duration);
}
Console.WriteLine("Done with the default interface method for IBlinkingLight.Blink.");
}
}
Die Standardimplementierung ermöglicht jeder Leuchte das Blinken. Die Deckenleuchte kann sowohl Timer- als
auch Blinkfunktionen mit der Standardimplementierung hinzufügen:
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
Ein neuer Leuchtentyp ( LEDLight ) unterstützt die Timerfunktion und die Blinkfunktion direkt. Dieser
Leuchtenstil implementiert sowohl die ITimerLight - als auch die IBlinkingLight -Schnittstelle und überschreibt
die Blink -Methode:
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
Das HalogenLight -Element, das Sie zuvor erstellt haben, unterstützt kein Blinken. Fügen Sie IBlinkingLight
daher nicht der Liste der unterstützten Schnittstellen dieses Elements hinzu.
Diese Änderungen werden ordnungsgemäß kompiliert, auch wenn ExtraFancyLight Unterstützung für die
ILight -Schnittstelle und die beiden abgeleiteten Schnittstellen ITimerLight und IBlinkingLight deklariert. Es
gibt nur eine „nächste“ Implementierung, die in der ILight -Schnittstelle deklariert ist. Jede Klasse, die eine
Überschreibung deklariert hat, würde zur „nächsten“ Implementierung werden. Sie haben in den
vorhergehenden Klassen Beispiele gesehen, die die Member anderer abgeleiteter Schnittstellen überschreiben.
Vermeiden Sie das Überschreiben derselben Methode in mehreren abgeleiteten Schnittstellen. Auf diese Weise
wird ein mehrdeutiger Methodenaufruf erstellt, wenn eine Klasse beide abgeleiteten Schnittstellen
implementiert. Der Compiler kann keine einzelne bessere Methode auswählen, sodass er einen Fehler ausgibt.
Wenn z.B. sowohl IBlinkingLight als auch ITimerLight eine Überschreibung von PowerStatus implementiert
hat, müsste OverheadLight eine spezifischere Überschreibung bereitstellen. Andernfalls kann der Compiler nicht
zwischen den Implementierungen in den beiden abgeleiteten Schnittstellen wählen. Sie können diese Situation
in der Regel vermeiden, indem Sie Schnittstellendefinitionen klein halten und sich auf eine Funktion
konzentrieren. In diesem Szenario ist jede Funktion einer Leuchte eine eigene Schnittstelle. Mehrere
Schnittstellen werden nur von Klassen geerbt.
Dieses Beispiel zeigt ein Szenario, in dem Sie diskrete Features definieren können, die in Klassen gemischt
werden können. Sie deklarieren einen beliebigen Satz unterstützter Funktionen, indem Sie deklarieren, welche
Schnittstellen eine Klasse unterstützt. Durch die Verwendung von virtuellen Standardschnittstellenmethoden
können Klassen eine andere Implementierung für beliebige oder alle Schnittstellenmethoden verwenden oder
definieren. Diese Sprachfunktion bietet neue Möglichkeiten zum Modellieren der realen Systeme, die Sie
entwickeln. Standardschnittstellenmethoden bieten eine bessere Möglichkeit, verwandte Klassen auszudrücken,
die Mix-and-Match-Funktionen verwenden, indem sie virtuelle Implementierungen dieser Funktionen
verwenden.
Indizes und Bereiche
04.11.2021 • 6 minutes to read
Bereiche und Indizes bieten eine prägnante Syntax für den Zugriff auf einzelne Elemente oder Bereiche in einer
Sequenz.
In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:
Verwenden Sie die Syntax für Bereiche in einer Sequenz.
Lernen Sie die Entwurfsentscheidungen für Start und Ende jeder Sequenz kennen.
Lernen Sie Szenarien für die Typen Index und Range kennen.
Sie können das letzte Wort mit dem ^1 -Index abrufen. Fügen Sie unter der Initialisierung folgenden Code
hinzu:
Ein Bereich gibt den Beginn und das Ende eines Bereichs an. Bereiche sind exklusiv, d. h. das Ende ist nicht im
Bereich enthalten. Der Bereich [0..^0] stellt ebenso wie [0..sequence.Length] den gesamten Bereich dar.
Der folgende Code erzeugt einen Teilbereich mit den Worten „quick“, „brown“ und „fox“. Er enthält words[1] bis
words[3] . Das Element words[4] befindet sich nicht im Bereich. Fügen Sie derselben Methode den folgenden
Code hinzu. Kopieren Sie ihn, und fügen Sie ihn unten in das interaktive Fenster ein.
string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();
Der folgende Code gibt den Bereich mit „lazy“ und „dog“ zurück. Dazu gehören words[^2] und words[^1] . Der
Endindex words[^0] ist nicht enthalten. Fügen Sie den folgenden Code auch hinzu:
Die folgenden Beispiele erstellen Bereiche, die am Anfang, am Ende und auf beiden Seiten offen sind:
Sie können Bereiche oder Indizes auch als Variablen deklarieren. Die Variable kann dann innerhalb der Zeichen
[ und ] verwendet werden:
Das folgende Beispiel zeigt viele der Gründe für diese Auswahl. Ändern Sie x , y und z , um verschiedene
Kombinationen zu testen. Verwenden Sie beim Experimentieren Werte, wo x kleiner ist als y und y kleiner
als z für gültige Kombinationen. Fügen Sie den folgenden Code in einer neuen Methode hinzu. Probieren Sie
verschiedene Kombinationen aus:
int[] numbers = Enumerable.Range(0, 100).ToArray();
int x = 12;
int y = 25;
int z = 36;
IMPORTANT
Die Codeleistung bei Verwendung eines Bereichsoperators hängt vom Typ des Operanden der Sequenz ab.
Die Zeitkomplexität des Bereichsoperators hängt vom Sequenztyp ab. Wenn die Sequenz beispielsweise string oder ein
Array ist, ist das Ergebnis eine Kopie des angegebenen Abschnitts der Eingabe, die Zeitkomplexität ist also O(N) . N steht
dabei für die Länge des Bereichs. Wenn es sich andernfalls um System.Span<T> oder System.Memory<T> handelt,
verweist das Ergebnis auf denselben Sicherungsspeicher, d. h. es gibt keine Kopie, und für den Vorgang gilt O(1) .
Zusätzlich zur Zeitkomplexität führt dies zu weiteren Belegungen und Kopien, was sich auf die Leistung auswirkt. Bei
leistungsabhängigem Code sollten Sie als Sequenztyp Span<T> oder Memory<T> verwenden, da der Bereichsoperator
keine Belegungen dafür vornimmt.
Ein Typ ist zählbar , wenn er über eine Eigenschaft mit dem Namen Length oder Count mit einem zugreifbaren
Getter und einem Rückgabetyp von int verfügt. Ein zählbarer Typ, der Indizes oder Bereiche nicht explizit
unterstützt, kann implizite Unterstützung dafür bieten. Weitere Informationen finden Sie in den Abschnitten
Implizite Indexunterstützung und Implizite Bereichsunterstützung der Featurevorschläge. Bereiche, die die
implizite Bereichsunterstützung verwenden, geben denselben Sequenztyp wie die Quellsequenz zurück.
Beispielsweise unterstützen die folgenden .NET-Typen Indizes und Bereiche: String, Span<T> und
ReadOnlySpan<T>. List<T> unterstützt Indizes, jedoch keine Bereiche.
Array zeigt ein differenzierteres Verhalten. Eindimensionale Arrays unterstützen sowohl Indizes als auch
Bereiche. Mehrdimensionale Arrays unterstützen keine Indexer oder Bereiche. Der Indexer für ein
mehrdimensionales Array verfügt über mehrere Parameter, nicht über einen einzelnen Parameter. Jagged Arrays,
auch als Array von Arrays bezeichnet, unterstützen sowohl Bereiche als auch Indexer. Das folgende Beispiel zeigt,
wie ein rechteckiger Unterabschnitt eines Jagged Arrays durchlaufen wird. Es durchläuft den Abschnitt in der
Mitte, wobei die ersten und letzten drei Zeilen sowie die ersten und letzten zwei Spalten jeder ausgewählten
Zeile ausgeschlossen werden:
In allen Fällen ordnet der Bereichsoperator für Array ein Array zu, um die zurückgegebenen Elemente zu
speichern.
(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);
C# 8.0 führt Nullable-Verweistypen ein, die Verweistypen auf die gleiche Weise ergänzen, wie Nullable-
Werttypen Werttypen ergänzen. Sie deklarieren eine Variable zu einem Ver weistyp, der NULL-Wer te
zulässt , indem Sie ? an den Typen anfügen. Beispielsweise stellt string? eine string dar, die NULL-Werte
zulässt. Mit diesen neuen Typen können Sie Ihre Entwurfsabsicht besser zum Ausdruck bringen: Einige Variablen
müssen immer einen Wert haben, bei anderen kann ein Wert fehlen.
In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:
Integrieren von Verweistypen, die NULL-Werte zulassen und nicht zulassen, in Ihre Entwürfe.
Aktivieren von Überprüfungen Ihres Verweistypen, der NULL-Werte zulässt, anhand Ihres Codes.
Schreiben von Code, bei dem ein Compiler diese Entwurfsentscheidungen erzwingt.
Verwenden des Verweisfeatures, das NULL-Werte zulässt, in Ihren eigenen Entwürfen.
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten, einschließlich des C# 8.0-Compilers. Der
C# 8.0-Compiler ist mit Visual Studio 2019 oder .NET Core 3.0 verfügbar.
In diesem Tutorial wird vorausgesetzt, dass Sie C# und .NET, einschließlich Visual Studio oder die .NET Core-CLI
kennen.
<Nullable>enable</Nullable>
Vor .NET 6 ist das -Element in neuen Projekten nicht enthalten. Ab .NET 6 enthalten neue Projekte das
Nullable
<Nullable>enable</Nullable> -Element in der Projektdatei.
Der Compiler interpretiert jede Verweistyp-Variablendeklaration für Code in einem aktivierten Nullable-
Anmerkungskontext als Nicht-Nullable-Ver weistyp . Sie können Ihre erste Warnung sehen, indem Sie
Eigenschaften für den Fragetext und die Art der Frage hinzufügen, wie im folgenden Code gezeigt:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
Da Sie QuestionText noch nicht initialisiert haben, gibt der Compiler eine Warnung aus, dass noch keine
Eigenschaft initialisiert wurde, nicht keine NULL-Werte zulässt. Ihr Entwurf verlangt, dass der Fragetext nicht null
ist. Also fügen Sie einen Konstruktor zur Initialisierung und den Wert QuestionType hinzu. Die fertige
Klassendefinition sieht wie im folgenden Code aus:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
Durch das Hinzufügen des Konstruktors wird die Warnung entfernt. Das Konstruktorargument ebenfalls ein
Verweistyp, der keine NULL-Werte zulässt. Der Compiler gibt also keine Warnungen aus.
Erstellen Sie eine public -Klasse mit dem Namen SurveyRun . Diese Klasse enthält eine Liste von
SurveyQuestion -Objekten und Methoden, um Fragen zur Umfrage hinzuzufügen, wie im folgenden Code
gezeigt:
using System.Collections.Generic;
namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();
Wie bisher müssen Sie das Listenobjekt Wert initialisieren, der keine NULL-Werte zulässt, oder der Compiler
gibt eine Warnung aus. Es gibt keine NULL-Überprüfungen in der zweiten Überladung von AddQuestion , da sie
nicht benötigt werden: Sie haben diese Variable als Typ deklariert, der keine NULL-Werte zulässt. Der Wert kann
nicht null sein.
Wechseln Sie in Ihrem Editor zu Program.cs, und ersetzen Sie den Inhalt von Main durch die folgenden
Codezeilen:
Da sich das gesamte Projekt in einem aktivierten Nullable-Anmerkungskontext befindet, erhalten Sie
Warnungen, wenn Sie null in Erwartung eines Nicht-Nullable-Verweistyps an eine Methode übergeben.
Probieren Sie es aus, indem Sie die folgende Zeile zu Main hinzufügen:
surveyRun.AddQuestion(QuestionType.Text, default);
Fügen Sie als Nächstes eine static -Methode hinzu, um neuen Teilnehmer zu erstellen, indem Sie eine Zufalls-
ID generieren:
Die Hauptaufgabe dieser Klasse besteht darin, die Antworten für einen Teilnehmer auf die Fragen der Umfrage
zu generieren. Diese Aufgabe umfasst einige Schritte:
1. Bitten Sie um die Teilnahme an der Umfrage. Wenn eine Person nicht einverstanden ist, geben Sie eine
fehlende (oder Null) Antwort zurück.
2. Stellen Sie die einzelnen Fragen, und notieren Sie die Antwort. Jede Antwort kann auch nicht vorhanden
(oder null) sein.
Fügen Sie der SurveyResponse -Klasse den folgenden Code hinzu:
private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}
Der Speicher für Umfrageantworten ist eine Dictionary<int, string>? . Damit wird angegeben, dass auch NULL
zulässig ist. Sie verwenden das neue Sprachfeature, um Ihre Entwurfsabsicht zu deklarieren, sowohl gegenüber
dem Compiler als auch gegenüber allen, die Ihren Code später lesen. Wenn Sie jemals surveyResponses
dereferenzieren, ohne zuerst nach dem null -Wert zu suchen, erhalten Sie eine Compilerwarnung. Sie erhalten
keine Warnung in der AnswerSurvey -Methode, da der Compiler bestimmen kann, dass die Variable
surveyResponses oben auf einen Wert gesetzt wurde, der NULL-Werte zulässt.
Die Verwendung von null für fehlende Antworten hebt einen wichtigen Punkt für die Arbeit mit NULL-Werte
zulassenden Verweistypen hervor: Ihr Ziel ist nicht, alle null -Werte aus Ihrem Programm zu entfernen. Ihr Ziel
ist eher, sicherzustellen, dass der Code, den Sie schreiben, Ihre Entwurfsabsicht ausdrückt. Fehlende Werte sind
ein notwendiges in Ihrem Code auszudrückendes Konzept. Der null -Wert ist eine klare Möglichkeit, diese
fehlenden Werte auszudrücken. Der Versuch, alle null -Werte zu entfernen, führt nur zum Definieren einer
anderen Möglichkeit, diese fehlenden Werte ohne null auszudrücken.
Als Nächstes müssen Sie die PerformSurvey -Methode in der SurveyRun -Klasse schreiben. Fügen Sie den
folgenden Code der SurveyRun -Klasse hinzu:
Auch hier geben Sie durch Ihre Auswahl von List<SurveyResponse>? , die NULL-Werte zulässt, dass die Antwort
Null sein kann. Damit wird angegeben, dass die Umfrage noch keinem Befragten zugewiesen wurde. Beachten
Sie, dass Personen hinzugefügt werden, bis genügend zugestimmt haben.
Der letzte Schritt zum Ausführen der Umfrage besteht darin, einen Aufruf zur Durchführung der Umfrage am
Ende der Main -Methode hinzuzufügen:
surveyRun.PerformSurvey(50);
Da surveyResponses ein Verweistyp ist, der NULL-Werte zulässt, sind vor dem Dereferenzieren NULL-
Überprüfungen erforderlich. Die Answer -Methode gibt eine Zeichenfolge zurück, die keine NULL-Werte zulässt.
Daher müssen wir durch die Verwendung des NULL-Sammeloperators den Fall abdecken, dass eine Antwort
fehlt.
Fügen Sie diese drei Ausdruckskörpermember zur SurveyRun -Klasse hinzu:
Das AllParticipants Member muss berücksichtigen, dass die respondents -Variable Null sein kann, der
zurückgegebene Wert aber nicht Null sein darf. Wenn Sie diesen Ausdruck ändern, indem Sie ?? und die
folgende leere Sequenz entfernen, warnt Sie der Compiler, dass die Methode null zurückgeben könnte, und
ihre Rückgabesignatur gibt einen Typen zurück, der keine NULL-Werte zulässt.
Fügen Sie abschließend die folgende Schleife am Ende der Main -Methode hinzu:
foreach (var participant in surveyRun.AllParticipants)
{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
}
}
else
{
Console.WriteLine("\tNo responses");
}
}
Sie benötigen keine null -Überprüfungen in diesem Code, das Sie die darunter liegenden Schnittstellen so
entworfen haben, dass sie alle einen Verweistypen zurückgeben, der keine NULL-Werte zulässt.
Nächste Schritte
Hier erfahren Sie, wie Sie Nullable-Verweistypen bei Verwendung von Entity Framework nutzen:
C# 8.0 führt asynchrone Streams ein, die eine Streamingdatenquelle modellieren. In Datenströmen werden
Elemente häufig asynchron abgerufen oder generiert. Asynchrone Streams basieren auf neuen Schnittstellen,
die in .NET Standard 2.1 eingeführt wurden. Diese Schnittstellen werden in .NET Core 3.0 und höher unterstützt.
Sie stellen ein intuitives Programmiermodell für asynchrone Streamingdatenquellen bereit.
In diesem Tutorial lernen Sie, wie die folgenden Aufgaben ausgeführt werden:
Erstellen einer Datenquelle, die eine Sequenz von Datenelementen asynchron generiert
Asynchrones Nutzen dieser Datenquelle
Unterstützung für Abbruchvorgänge und erfasste Kontexte für asynchrone Streams
Erkennen, wenn die neue Schnittstelle und Datenquelle früheren synchronen Datensequenzen vorgezogen
werden
Voraussetzungen
Sie müssen Ihren Computer zur Ausführung von .NET Core einrichten, einschließlich des C# 8.0-Compilers. Der
C# 8-Compiler steht ab Visual Studio 2019 Version 16.3 oder mit dem .NET Core 3.0 SDK zur Verfügung.
Sie müssen ein GitHub-Zugriffstoken erstellen, damit Sie auf den GitHub GraphQL-Endpunkt zugreifen können.
Wählen Sie die folgenden Berechtigungen für Ihr GitHub-Zugriffstoken aus:
repo:status
public_repo
Speichern Sie das Zugriffstoken an einem sicheren Ort, damit Sie es für den Zugriff auf den GitHub-API-
Endpunkt verwenden können.
WARNING
Schützen Sie Ihr persönliches Zugriffstoken. Jede Software mit Ihrem persönlichen Zugriffstoken kann mit Ihren
Zugriffsrechten GitHub-API-Aufrufe ausführen.
In diesem Tutorial wird vorausgesetzt, dass Sie C# und .NET, einschließlich Visual Studio oder die .NET Core-CLI
kennen.
try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
}
Sie können entweder eine GitHubKey -Umgebungsvariable auf Ihr persönliches Zugriffstoken festlegen, oder Sie
können das letzte Argument im Aufruf von GetEnvVariable durch Ihr persönliches Zugriffstoken ersetzen.
Fügen Sie Ihren Zugriffscode nicht in den Quellcode ein, wenn Sie die Quelle für andere freigeben. Laden Sie
Zugriffscodes niemals in ein freigegebenes Quellrepository hoch.
Nach dem Erstellen des GitHub-Clients werden durch den Code in Main ein Objekt für Fortschrittsberichte und
ein Abbruchtoken erstellt. Nachdem die Objekte erstellt wurden, wird runPagedQueryAsync durch Main
aufgerufen, um die neuesten 250 Issues abzurufen. Nach Abschluss dieser Aufgabe werden die Ergebnisse
angezeigt.
Bei Ausführung der Startanwendung können Sie einige wichtige Details zur Ausführung dieser Anwendung
beobachten. Für jede von GitHub zurückgegebene Seite wird der Fortschritt gemeldet. Sie können eine deutliche
Pause beobachten, bevor GitHub eine weitere neue Seite mit Issues zurückgibt. Die Issues werden erst angezeigt,
nachdem alle zehn Seiten aus GitHub abgerufen wurden.
Konzentrieren wir uns auf den Paginierungsalgorithmus und die asynchrone Struktur des obigen Codes. (Details
zur GitHub GraphQL-API finden Sie in der GitHub GraphQL-Dokumentation.) Die runPagedQueryAsync -Methode
listet die Issues vom neuesten zum ältesten auf. Sie fordert 25 Issues pro Seite an und untersucht die pageInfo -
Struktur der Antwort, um mit der vorherigen Seite fortzufahren. Dies entspricht der GraphQL-
Standardpaginierungsunterstützung für mehrseitige Antworten. Die Antwort enthält ein pageInfo -Objekt mit
einem hasPreviousPages -Wert und einem startCursor -Wert zum Anfordern der vorherigen Seite. Die Issues
befinden sich im nodes -Array. Die runPagedQueryAsync -Methode fügt diese Knoten einem Array an, das alle
Ergebnisse aus allen Seiten enthält.
Nach dem Abrufen und Wiederherstellen einer Seite mit Ergebnissen meldet runPagedQueryAsync den
Fortschritt und prüft auf Abbruch. Wenn ein Abbruch angefordert wurde, löst runPagedQueryAsync eine
OperationCanceledException aus.
Es gibt mehrere Elemente in diesem Code, die verbessert werden können. Vor allem muss runPagedQueryAsync
Speicherplatz für alle zurückgegebenen Issues zuordnen. In diesem Beispiel wird der Vorgang bei 250 Issues
beendet, weil das Abrufen aller offenen Issues wesentlich mehr Arbeitsspeicher zum Speichern aller
abgerufenen Issues erfordern würde. Der Algorithmus ist durch die Protokolle zur Unterstützung von
Fortschrittsberichten und Abbruchvorgängen beim ersten Lesen schwieriger zu verstehen. Es sind mehr Typen
und APIs beteiligt. Außerdem müssen Sie die Kommunikation über CancellationTokenSource und die zugehörige
CancellationToken-Struktur verfolgen, um nachzuvollziehen, wo der Abbruch angefordert und wo er gewährt
wird.
Diese neuen Sprachfeatures hängen von drei neuen Schnittstellen ab, die dem .NET Standard 2.1 hinzugefügt
und in .NET Core 3.0 implementiert wurden:
System.Collections.Generic.IAsyncEnumerable<T>
System.Collections.Generic.IAsyncEnumerator<T>
System.IAsyncDisposable
Diese drei Schnittstellen sollten den meisten C#-Entwicklern vertraut sein. Sie verhalten sich ähnlich wie ihre
synchronen Gegenstücke:
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
System.IDisposable
Ein möglicherweise weniger bekannter Typ ist System.Threading.Tasks.ValueTask. Die ValueTask -Struktur bietet
eine ähnliche API für die System.Threading.Tasks.Task-Klasse. ValueTask wird in diesen Schnittstellen aus
Leistungsgründen verwendet.
Der Startcode verarbeitet die einzelnen Seiten, während sie abgerufen werden, wie im folgenden Code gezeigt:
finalResults.Merge(issues(results)["nodes"]);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
Sie können auch die Deklaration von finalResults weiter oben in dieser Methode sowie die return -
Anweisung entfernen, die der von Ihnen geänderten Schleife folgt.
Sie haben die Änderungen zum Generieren eines asynchronen Datenstroms abgeschlossen. Die fertige Methode
sollte in etwa dem folgenden Code entsprechen:
private static async IAsyncEnumerable<JToken> runPagedQueryAsync(GitHubClient client,
string queryText, string repoName)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
Als Nächstes ändern Sie den Code, der die Sammlung nutzt, um den asynchronen Datenstrom zu verwenden.
Suchen Sie in Main den folgenden Code, der die Sammlung der Issues verarbeitet:
try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
Ersetzen Sie den Code durch die folgende await foreach -Schleife:
int num = 0;
await foreach (var issue in runPagedQueryAsync(client, PagedIssueQuery, "docs"))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
Die neue Schnittstelle IAsyncEnumerator<T> leitet von IAsyncDisposable ab. Das bedeutet, dass die
vorhergehende Schleife den Stream asynchron löscht, wenn die Schleife beendet wird. Die Schleife ähnelt dem
folgenden Code:
int num = 0;
var enumerator = runPagedQueryAsync(client, PagedIssueQuery, "docs").GetEnumeratorAsync();
try
{
while (await enumerator.MoveNextAsync())
{
var issue = enumerator.Current;
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
} finally
{
if (enumerator != null)
await enumerator.DisposeAsync();
}
Standardmäßig werden Streamelemente im erfassten Kontext verarbeitet. Wenn Sie die Erfassung des Kontexts
deaktivieren möchten, verwenden Sie die Erweiterungsmethode
TaskAsyncEnumerableExtensions.ConfigureAwait. Weitere Informationen über Synchronisierungskontexte und
die Erfassung des aktuellen Kontexts finden Sie im Artikel über das Verwenden des aufgabenbasierten
asynchronen Musters.
Asynchrone Streams unterstützen Abbruchvorgänge mithilfe desselben Protokolls wie andere async -
Methoden. Ändern Sie die Signatur für die asynchrone Iteratormethode folgendermaßen, damit
Abbruchvorgänge unterstützt werden:
private static async IAsyncEnumerable<JToken> runPagedQueryAsync(GitHubClient client,
string queryText, string repoName, [EnumeratorCancellation] CancellationToken cancellationToken =
default)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
Sie können den Code für das abgeschlossene Tutorial aus dem Repository dotnet/docs im Ordner csharp/whats-
new/tutorials abrufen.
Dieses Tutorial erläutert, wie Sie die Zeichenfolgeninterpolation in C# verwenden, um Werte in eine einzelne
Ergebniszeichenfolge einzufügen. Sie schreiben einen C#-Code und sehen dort die Ergebnisse der Kompilierung
und Ausführung Ihres Codes. Dieses Tutorial enthält einige Lektionen, in denen Ihnen gezeigt wird, wie Werte in
eine Zeichenfolge eingefügt und auf verschiedene Weisen formatiert werden.
Für dieses Tutorial wird vorausgesetzt, dass Sie über einen Computer verfügen, den Sie für die Entwicklung
nutzen können. Im .NET-Tutorial Hello World in 10 minutes (Hallo Welt in zehn Minuten) finden Sie eine
Anleitung zum Einrichten Ihrer lokalen Entwicklungsumgebung unter Windows, Linux oder macOS. Sie können
auch die interaktive Version dieses Tutorials in Ihrem Browser durcharbeiten.
Testen Sie diesen Code, indem Sie dotnet run in Ihr Konsolenfenster eingeben. Wenn Sie das Programm
ausführen, wird eine einzelne Zeichenfolge angezeigt, die Ihren Namen in der Begrüßung enthält. Die im
WriteLine-Methodenaufruf enthaltene Zeichenfolge ist ein interpolierter Zeichenfolgenausdruck. Dabei handelt
es sich um eine Vorlage, durch die Sie eine einzelne Zeichenfolge (als Ergebniszeichenfolge bezeichnet) aus
einer Zeichenfolge erstellen können, die eingebetteten Code enthält. Interpolierte Zeichenfolgen sind besonders
nützlich für das Einfügen von Werten in eine Zeichenfolge oder zum Verketten (bzw. Verknüpfen) von
Zeichenfolgen.
Dieses einfache Beispiel enthält die zwei Elemente, über die jede interpolierte Zeichenfolge verfügen muss:
Ein Zeichenfolgenliteral, das ein $ -Zeichen vor dem öffnenden Anführungszeichen enthält. Zwischen
dem $ -Symbol und dem Anführungszeichen darf kein Leerraum vorhanden sein. (Wenn Sie testen
möchten, was andernfalls geschieht, fügen Sie einen Leerraum nach dem $ -Zeichen ein, speichern Sie
die Datei, und führen Sie das Programm erneut aus, indem Sie dotnet run im Konsolenfenster eingeben.
Der C#-Compiler zeigt dann die Fehlermeldung „Fehler CS1056: Unerwartetes Zeichen '$'“ an.)
Mindestens ein Interpolationsausdruck. Ein Interpolationsausdruck wird durch eine öffnende und eine
schließende geschweifte Klammer ( { und } ) angegeben. Sie können jeden C#-Ausdruck in die
Klammern einfügen, der einen Wert (einschließlich null ) zurückgibt.
Im Folgenden finden Sie weitere Beispiele für die Zeichenfolgeninterpolation mit einigen anderen Datentypen.
Dann wird eine Instanz der Klasse Vegetable mit dem Namen item mithilfe des new Operators erstellt und ein
Name für den Konstruktor Vegetable angegeben:
Zum Schluss wird die Variable item in eine interpolierte Zeichenfolge einbezogen, die auch einen DateTime-
Wert, Decimal-Wert und einen Enumerationswert Unit enthält. Ersetzen Sie sämtlichen C#-Code in Ihrem
Editor durch folgenden Code, und verwenden Sie dann den dotnet run -Befehl, um diesen auszuführen:
using System;
Beachten Sie, dass der Interpolationsausdruck item der interpolierten Zeichenfolge in der Ergebniszeichenfolge
zu dem Text „eggplant“ aufgelöst wird. Dies liegt daran, dass der Ausdruckergebnistyp keine Zeichenfolge ist.
Daher wird das Ergebnis auf folgende Weise zu einer Zeichenfolge aufgelöst:
Wenn der Interpolationsausdruck zu null ausgewertet wird, wird eine leere Zeichenfolge („“ oder
String.Empty) verwendet.
Wenn der Interpolationsausdruck nicht zu null ausgewertet wird, wird in der Regel die Methode
ToString des Ergebnistyps aufgerufen. Sie können dies testen, indem Sie die Implementierung der
Methode Vegetable.ToString aktualisieren. Sie müssen die Methode ToString nicht einmal
implementieren, da jeder Typ auf die eine oder andere Art über eine Implementierung dieser Methode
verfügt. Sie können dies testen, indem Sie die Definition der Methode Vegetable.ToString im Beispiel
auskommentieren (fügen Sie hierzu davor ein Kommentarsymbol // ein). In der Ausgabe wird die
Zeichenfolge „eggplant“ durch den vollqualifizierten Typnamen ersetzt (in diesem Beispiel „Vegetable“).
Dies stellt das Standardverhalten der Object.ToString()-Methode dar. Das Standardverhalten der Methode
ToString für einen Enumerationswert besteht darin, die Zeichenfolgendarstellung eines Werts
zurückzugeben.
Bei der Ausgabe dieses Beispielcodes ist das Datum zu genau angegeben (der Preis von Auberginen ändert sich
nicht sekündlich), und der Wert der Variablen „price“ gibt keine Währungsinformation an. Im nächsten Abschnitt
erfahren Sie, wie Sie diese Probleme beheben, indem Sie das Format der Zeichenfolgendarstellung der
Ergebnisse des Ausdrucks steuern.
Sie können eine Formatzeichenfolge angeben, indem Sie dem Interpolationsausdruck einen Doppelpunkt („:“)
und die Formatzeichenfolge anfügen. Bei „d“ handelt es sich um eine Zeichenfolge für das Standardformat für
Datum und Uhrzeit, die das kurze Datumsformat darstellt. Bei „C2“ handelt es sich um eine Zeichenfolge für das
numerische Standardformat, die eine Zahl als Währungswert mit zwei Ziffern nach dem Dezimaltrennzeichen
darstellt.
Einige Typen in den .NET-Bibliotheken unterstützen einen vordefinierten Satz von Formatzeichenfolgen. Darin
sind alle numerischen Typen sowie alle Datums- und Uhrzeittypen enthalten. Eine vollständige Liste der Typen,
die Formatzeichenfolgen unterstützen, finden Sie im Artikel Formatieren von Typen in .NET unter Format Strings
and .NET Class Library Types (Formatzeichenfolgen und .NET-Klassenbibliothekstypen).
Versuchen Sie, die Formatzeichenfolgen in Ihrem Text-Editor zu ändern, und führen Sie das Programm jedes Mal
erneut aus, wenn Sie eine Änderung vornehmen. So können Sie feststellen, wie sich die Änderungen auf die
Formatierung des Datums, der Uhrzeit und des numerischen Werts auswirken. Ändern Sie „d“ in {date:d} in „t“
(um das kurze Uhrzeitformat anzuzeigen) sowie in „y“ (um das Jahr und den Monat anzuzeigen) und in „yyyy“
(um das Jahr als vierstellige Zahl anzuzeigen). Ändern Sie „C2“ in {price:C2} in „e“ (für die
Exponentialschreibweise) und in „F3“ (für einen numerischen Wert mit drei Ziffern nach dem
Dezimaltrennzeichen).
Sie können zusätzlich zum Steuern der Formatierung auch die Feldbreite und die Ausrichtung der formatierten
Zeichenfolgen steuern, die in der Ergebniszeichenfolge enthalten sind. Im nächsten Abschnitt erfahren Sie mehr
zu diesem Thema.
using System;
using System.Collections.Generic;
Die Namen der Autoren sind linksbündig ausgerichtet, während deren Werke rechtsbündig ausgerichtet sind.
Sie können die Ausrichtung festlegen, indem Sie einem Interpolationsausdruck ein Komma („,“) anfügen und die
minimale Feldbreite angeben. Wenn der angegebene Wert eine positive Zahl ist, wird das Feld rechtsbündig
ausgerichtet. Wenn er eine negative Zahl ist, wird das Feld linksbündig ausgerichtet.
Versuchen Sie die negativen Vorzeichen aus dem Code {"Author",-25} und {title.Key,-25} wie im folgenden
Code zu entfernen, und führen Sie das Beispiel erneut aus:
Console.WriteLine($"|{"Author",25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");
In diesem Tutorial erfahren Sie, wie Sie die Zeichenfolgeninterpolation verwenden, um Ausdrucksergebnisse zu
einer Ergebniszeichenfolge hinzuzufügen. Für diese Beispiele wird davon ausgegangen, dass Sie sich mit den
grundlegenden C#-Konzepten und der .NET-Typformatierung auskennen. Wenn Sie sich mit der
Zeichenfolgeninterpolation oder der .NET-Typformatierung nicht auskennen, absolvieren Sie zuerst das
interaktive Tutorial zur Zeichenfolgeninterpolation. Weitere Informationen zum Formatieren von Typen in .NET
finden Sie unter Formatieren von Typen in .NET.
NOTE
Die C#-Beispiele in diesem Artikel werden in der Inlinecodeausführung und dem Playground von Try.NET ausgeführt.
Klicken Sie auf die Schaltfläche Ausführen , um ein Beispiel in einem interaktiven Fenster auszuführen. Nachdem Sie den
Code ausgeführt haben, können Sie ihn ändern und den geänderten Code durch erneutes Anklicken der Schaltfläche
Ausführen ausführen. Der geänderte Code wird entweder im interaktiven Fenster ausgeführt, oder das interaktive
Fenster zeigt alle C#-Compilerfehlermeldungen an, wenn die Kompilierung fehlschlägt.
Einführung
Das Feature Zeichenfolgeninterpolation ist ein Zusatz zum Feature composite formating (Kombinierte
Formatierung) und ermöglicht eine Syntax, die lesbarer und zweckmäßiger ist, um formatierte
Ausdrucksergebnisse zu einer Ergebniszeichenfolge hinzuzufügen.
Wenn Sie ein Zeichenfolgenliteral als interpolierte Zeichenfolge ermitteln möchten, stellen Sie ihm ein $ -
Symbol voran. Sie können jeden gültigen C#-Ausdruck einbetten, der einen Wert in einer interpolierten
Zeichenfolge zurückgibt. Im folgenden Beispiel wird das Ergebnis eines Ausdrucks in eine Zeichenfolge
konvertiert und zu einer Ergebniszeichenfolge hinzugefügt, sobald ein Ausdruck berechnet wird:
double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}");
Console.WriteLine($"Length of the hypotenuse of the right triangle with legs of {a} and {b} is
{CalculateHypotenuse(a, b)}");
double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);
// Expected output:
// Area of the right triangle with legs of 3 and 4 is 6
// Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5
Wie in dem Beispiel dargestellt, fügen Sie einen Ausdruck zu einer interpolierten Zeichenfolge hinzu, indem Sie
diesen in Klammern setzen:
{<interpolationExpression>}
Interpolierte Zeichenfolgen unterstützen sämtliche Funktionen des Features kombinierte Formatierung von
Zeichenfolgen. Damit stellen sie eine lesbarere Alternative zur Verwendung der String.Format-Methode dar.
{<interpolationExpression>:<formatString>}
// Expected output:
// On Sunday, November 25, 1731 Leonhard Euler introduced the letter e to denote 2.71828 in a letter to
Christian Goldbach.
{<interpolationExpression>,<alignment>}
Wenn der Wert der Ausrichtung positiv ist, wird das formatierte Ausdrucksergebnis rechtsbündig ausgerichtet.
Ist der Wert negativ, wird das Ergebnis linksbündig ausgerichtet.
Wenn Sie sowohl die Ausrichtung als auch die Formatzeichenfolge angeben müssen, beginnen Sie mit der
Ausrichtungskomponente:
{<interpolationExpression>,<alignment>:<formatString>}
Im folgenden Beispiel sehen Sie, wie Sie die Ausrichtung angeben. Es werden senkrechte Striche („|“) verwendet,
um Textfelder zu begrenzen:
const int NameAlignment = -9;
const int ValueAlignment = 7;
double a = 3;
double b = 4;
Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:");
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|");
// Expected output:
// Three classical Pythagorean means of 3 and 4:
// |Arithmetic| 3.500|
// |Geometric| 3.464|
// |Harmonic | 3.429|
Wie Sie in der Beispielausgabe sehen können, wird der Wert Ausrichtung ignoriert, wenn die Länge des
formatierten Ausdrucksergebnisses über die angegebene Feldbreite hinausgeht.
Weitere Informationen finden Sie im Artikel Kombinierte Formatierung im Abschnitt Ausrichtungskomponente.
// Expected output:
// Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets.
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents
Wie in dem Beispiel dargestellt, können Sie eine FormattableString-Instanz verwenden, um mehrere
Ergebniszeichenfolgen für verschiedene Kulturen zu generieren.
Zusammenfassung
In diesem Tutorial wurden häufig auftretende Szenarios zur Verwendung der Zeichenfolgeninterpolation
beschrieben. Weitere Informationen zur Zeichenfolgeninterpolation finden Sie unter Zeichenfolgeninterpolation.
Weitere Informationen zum Formatieren von Typen in .NET finden Sie unter Formatieren von Typen in .NET und
Kombinierte Formatierung.
Siehe auch
String.Format
System.FormattableString
System.IFormattable
Zeichenfolgen
Konsolen-App
04.11.2021 • 12 minutes to read
In diesem Tutorial lernen Sie verschiedene Features in .NET Core und der Sprache C# kennen. Es werden die
folgenden Themen abgedeckt:
Die Grundlagen zur .NET Core-CLI
Die Struktur einer C#-Konsolenanwendung
Konsolen-E/A
Grundlagen der Datei-E/A-APIs in .NET
Grundlagen des taskbasierten asynchronen Programmiermodells in .NET
Sie erstellen eine Anwendung, die eine Textdatei liest und die Inhalte dieser Textdatei an die Konsole ausgibt. Das
Tempo der Ausgabe in der Konsole ist so festgelegt, dass ein lautes Mitlesen möglich ist. Sie können die
Ausgabe beschleunigen oder verlangsamen, indem Sie die Tasten „<“ (kleiner als) oder „>“ (größer als) drücken.
In diesem Tutorial werden viele Features abgedeckt. Gehen wir sie einzeln an.
Voraussetzungen
Richten Sie Ihren Computer für die Ausführung von .NET Core ein. Die Installationsanweisungen finden
Sie auf der Seite .NET Core-Downloads. Sie können diese Anwendung unter Windows, Linux, macOS oder
in einem Docker-Container ausführen.
Installieren Sie Ihren bevorzugten Code-Editor.
using System;
Diese Anweisung informiert den Compiler, dass alle Typen aus dem System -Namespace im Gültigkeitsbereich
liegen. Wie andere objektorientierte Sprachen, die Sie vielleicht schon verwendet haben, verwendet C#
Namespaces, um Typen zu organisieren. Dieses Hello World-Programm funktioniert genauso. Sie können sehen,
dass das Programm in den Namespace eingeschlossen ist, wobei der Name auf dem des aktuellen
Verzeichnisses basiert. Für dieses Tutorial ändern wir den Namen des Namespace in TeleprompterConsole :
namespace TeleprompterConsole
Diese Methode verwendet Typen aus zwei neuen Namespaces. Für die Kompilierung müssen Sie oben in der
Datei die folgenden zwei Zeilen einfügen:
using System.Collections.Generic;
using System.IO;
Führen Sie das Programm (unter Verwendung von dotnet run ) aus. Jede Zeile wird einzeln an der Konsole
ausgegeben.
Als Nächstes ändern Sie die Art und Weise, in der die Dateizeilen verarbeitet werden und fügen nach jedem
geschriebenen Wort eine Verzögerung hinzu. Ersetzen Sie die Console.WriteLine(line) -Anweisung in der Main
-Methode durch den folgenden Block:
Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}
Die Task-Klasse ist im System.Threading.Tasks-Namespace enthalten, deshalb müssen Sie diese using -
Anweisung am Anfang der Datei hinzufügen:
using System.Threading.Tasks;
Führen Sie das Beispiel aus, und überprüfen Sie die Ausgabe. Jetzt folgt nach der Ausgabe jedes Worts eine
Pause von 200 ms. Die angezeigte Ausgabe ist jedoch fehlerhaft, weil der Quelltext mehrere Zeilen umfasst, die
mehr als 80 Zeichen ohne Zeilenumbruch aufweisen. Der Text ist möglicherweise schwer zu lesen, wenn er ohne
Umbrüche angezeigt wird. Dieses Problem kann einfach gelöst werden. Sie verfolgen einfach die Länge jeder
Zeile nach und generieren immer dann eine neue Zeile, wenn die Zeilenlänge einen bestimmten Schwellenwert
überschreitet. Deklarieren Sie nach der Deklaration von words in der ReadFrom -Methode eine lokale Variable,
die die Zeilenlänge enthält:
var lineLength = 0;
Fügen Sie dann nach der yield return word + " "; -Anweisung (vor der schließenden geschweiften Klammer)
den folgenden Code hinzu:
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Führen Sie das Beispiel aus. Jetzt sollten Sie in der Lage sein, den Text im festgelegten Tempo laut mitzulesen.
Asynchrone Tasks
In diesem letzten Schritt fügen Sie den Code hinzu, mit dem in einem Task die Ausgabe asynchron geschrieben
wird, während in einem weiteren Task Eingaben vom Benutzer gelesen werden, um ggf. die Geschwindigkeit der
Textanzeige zu erhöhen oder zu verringern oder die Textanzeige ganz zu beenden. Hierzu sind einige Schritte
erforderlich, damit Sie am Ende über alle benötigten Aktualisierungen verfügen. Im ersten Schritt erstellen Sie
eine asynchrone Task-Rückgabemethode, die den Code darstellt, den Sie bisher zum Lesen und Anzeigen der
Datei erstellt haben.
Fügen Sie diese Methode Ihrer Program -Klasse hinzu (diese stammt aus dem Textkörper Ihrer Main -Methode):
private static async Task ShowTeleprompter()
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(200);
}
}
}
Sie werden zwei Änderungen bemerken. Zunächst wird im Methodenkörper anstelle eines Aufrufs von Wait()
zum synchronen Warten auf eine Taskbeendigung in dieser Version das Schlüsselwort await verwendet. Hierzu
müssen Sie der Methodensignatur den async -Modifizierer hinzufügen. Diese Methode gibt einen Task zurück.
Beachten Sie, dass es keine return-Anweisungen gibt, die ein Task -Objekt zurückgeben. Stattdessen wird dieses
Task -Objekt durch Code erstellt, den der Compiler beim Verwenden des await -Operators generiert. Sie
können sich dies so vorstellen, dass die Methode eine Rückgabe durchführt, wenn sie ein await -Schlüsselwort
erreicht. Der zurückgegebene Task gibt an, dass der Vorgang noch nicht abgeschlossen wurde. Die Methode
wird fortgesetzt, wenn der erwartete Task abgeschlossen ist. Nach Abschluss der Ausführung weist der
zurückgegebene Task darauf hin, dass er abgeschlossen wurde. Der aufrufende Code kann den
zurückgegebenen Task überwachen, um zu ermitteln, wann dieser abgeschlossen ist.
Sie können diese neue Methode in Ihrer Main -Methode aufrufen:
ShowTeleprompter().Wait();
Hier führt der Code in Main einen asynchronen Wartevorgang aus. Sie sollten nach Möglichkeit anstelle eines
synchronen Wartevorgangs immer den await -Operator verwenden. In der Main -Methode einer
Konsolenanwendung kann der await -Operator jedoch nicht verwendet werden. Dies würde dazu führen, dass
die Anwendung beendet wird, bevor alle Tasks abgeschlossen sind.
NOTE
Wenn Sie C# 7.1 oder höher verwenden, können Sie Konsolenanwendungen mit der async Main -Methode erstellen.
Als Nächstes müssen Sie die zweite asynchrone Methode schreiben, um Inhalte aus der Konsole zu lesen und
auf die Tasteneingaben „<“ (kleiner als) und „>“ (größer als) sowie „X“ und „x“ zu überwachen. Dies ist die
Methode, die Sie für diesen Task hinzufügen:
private static async Task GetInput()
{
var delay = 200;
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
{
delay -= 10;
}
else if (key.KeyChar == '<')
{
delay += 10;
}
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
{
break;
}
} while (true);
};
await Task.Run(work);
}
Hiermit wird ein Lambdaausdruck zur Darstellung eines Action-Delegaten erstellt. Mit diesem wird ein Schlüssel
aus der Konsole gelesen und eine lokale Variable geändert, die die Verzögerung beim Drücken der Tasten „<“
(kleiner als) oder „>“ (größer als) durch den Benutzer darstellt. Die Delegatmethode wird beendet, wenn der
Benutzer die Tasten „X“ oder „x“ drückt, sodass der Benutzer die Textanzeige jederzeit beenden kann. Diese
Methode verwendet ReadKey() zum Blockieren und wartet darauf, dass der Benutzer eine Taste drückt.
Um dieses Feature abzuschließen, müssen Sie eine neue async Task -Rückgabemethode erstellen, die beide
Tasks ( GetInput und ShowTeleprompter ) startet und außerdem die von diesen Tasks gemeinsam verwendeten
Daten verwaltet.
Es muss eine neue Klasse erstellt werden, mit der die von diesen zwei Tasks gemeinsam verwendeten Daten
verarbeitet werden können. Diese Klasse enthält zwei öffentliche Eigenschaften: die Verzögerung und ein Flag
Done , mit dem angegeben wird, dass die Datei vollständig gelesen wurde:
namespace TeleprompterConsole
{
internal class TelePrompterConfig
{
public int DelayInMilliseconds { get; private set; } = 200;
Platzieren Sie diese Klasse in einer neuen Datei, und fügen Sie diese Klasse in den TeleprompterConsole -
Namespace ein (wie oben gezeigt). Sie benötigen außerdem eine using static -Anweisung, damit Sie ohne die
Namen der übergeordneten Klasse oder des Namespace auf die Min - und Max -Methode verweisen können.
Eine using static -Anweisung importiert die Methoden einer Klasse. Dies steht in Kontrast zu den bisher
verwendeten using -Anweisungen, die alle Klassen aus einem Namespace importiert haben.
Im nächsten Schritt müssen Sie die ShowTeleprompter - und GetInput -Methoden zur Verwendung des neuen
config -Objekts aktualisieren. Schreiben Sie einen finalen Task , der die async -Methode zurückgibt, um beide
Tasks zu starten und den Vorgang zu beenden, sobald der erste Task beendet wird:
Die neue Methode hier ist der WhenAny(Task[])-Aufruf. Hiermit wird ein Task erstellt, der abgeschlossen wird,
sobald einer der Tasks in dieser Argumentliste beendet ist.
Im nächsten Schritt müssen Sie die ShowTeleprompter - und GetInput -Methoden zur Verwendung des neuen
config -Objekts aktualisieren:
Diese neue Version von ShowTeleprompter ruft eine neue Methode in der TeleprompterConfig -Klasse auf. Jetzt
müssen Sie Main aktualisieren, um anstelle von ShowTeleprompter``RunTeleprompter aufzurufen:
RunTeleprompter().Wait();
Schlussbemerkung
In diesem Tutorial wurden verschiedene Features von C# und den .NET Core-Bibliotheken vorgestellt, die bei der
Arbeit in Konsolenanwendungen benötigt werden. Sie können auf diesem Wissen aufbauen, um C# und die hier
beschriebenen Klassen weiter zu erkunden. Sie haben die Grundlagen der Datei- und Konsolen-E/A
kennengelernt, und es wurden die blockierende und die nicht blockierende Verwendung der taskbasierten
asynchronen Programmierung vorgestellt. Außerdem haben Sie einen Überblick über die Sprache C# und die
Struktur von C#-Programmen erhalten, und Sie haben die .NET Core-CLI kennengelernt.
Weitere Informationen zur Datei-E/A finden Sie im Thema Datei- und Stream-E/A. Weitere Informationen zu
dem in diesem Tutorial verwendeten asynchronen Programmiermodell finden sie in den Themen
Aufgabenbasierte asynchrone Programmierung und Asynchrone Programmierung.
Tutorial: Übermitteln von HTTP-Anforderungen in
einer .NET-Konsolen-App mit C#
04.11.2021 • 8 minutes to read
In diesem Tutorial erstellen Sie eine App, die HTTP-Anforderungen an einen REST-Dienst in GitHub übermittelt.
Die App liest Informationen im JSON-Format und konvertiert den JSON-Code in C#-Objekte. Die Konvertierung
von JSON-Code in C#-Objekte wird als Deserialisierung bezeichnet.
In diesem Tutorial lernen Sie Folgendes:
Senden von HTTP-Anforderungen
Deserialisieren von JSON-Antworten
Konfigurieren der Deserialisierung mit Attributen
Wenn Sie lieber das vollständige Beispiel für dieses Tutorial befolgen möchten, können Sie es herunterladen.
Anweisungen zum Herunterladen finden Sie unter Beispiele und Lernprogramme.
Voraussetzungen
.NET SDK 5.0 oder höher
Ein Code-Editor wie Visual Studio Code, ein plattformübergreifender Open-Source-Editor Sie können die
Beispiel-App unter Windows, Linux oder macOS oder auch in einem Docker-Container ausführen.
Mit diesem Befehl werden die Startdateien für eine einfache „Hallo Welt“-App erstellt. Der Projektname
lautet „WebAPIClient“.
3. Navigieren Sie zum Verzeichnis „WebAPIClient“, und führen Sie die App aus.
cd WebAPIClient
dotnet run
Mit dotnet run wird automatisch dotnet restore ausgeführt, um alle Abhängigkeiten
wiederherzustellen, die von der App benötigt werden. Außerdem wird bei Bedarf dotnet build
ausgeführt.
2. Fügen Sie eine using -Direktive am Anfang der Datei Program.cs hinzu, damit der C#-Compiler den Typ
Task erkennt:
using System.Threading.Tasks;
Wenn Sie dotnet build zu diesem Zeitpunkt ausführen, wird die Kompilierung zwar erfolgreich
abgeschlossen, Sie werden jedoch gewarnt, dass diese Methode keine await -Operatoren enthält und
daher synchron ausgeführt wird. Sie fügen await -Operatoren später hinzu, wenn Sie die Methode
vervollständigen.
3. Ersetzen Sie die Main -Methode durch folgenden Code:
namespace WebAPIClient
{
class Program
{
private static readonly HttpClient client = new HttpClient();
using System.Net.Http;
using System.Net.Http.Headers;
dotnet run
Es wird keine Buildwarnung ausgegeben, da ProcessRepositories jetzt einen await -Operator enthält.
Die Ausgabe ist eine lange Darstellung von JSON-Text.
namespace WebAPIClient
{
public class Repository
{
public string name { get; set; }