Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 11 Min.

Primitive Obsession in C# vermeiden

Wie Sie mit einfachen Mitteln und einer zusätzlichen Zeile Code schon heute fehlende C#-Sprachfeatures ausgleichen und die Qualität des Quellcodes verbessern können.
© dotnetpro
Primitive Obsession (zu Deutsch ebenfalls primitive Obsession) ist programmiersprachenübergreifend ein Anti-Pattern beziehungsweise Code Smell [1] in der Softwareentwicklung, das dennoch in fast jeder C#-Codebase zu finden ist – und in der Regel zu hohen Aufwänden oder gar gravierenden und gleichzeitig schwer zu findenden Fehlern führt.Dieses Anti-Pattern ist dann erfüllt, wenn einfache Basis-Datentypen wie Booleans, Strings oder Integer verwendet werden, um komplexe Konzepte, Geschäftswerte beziehungsweise technische Werte zu repräsentieren, und sie damit folgende Probleme verursachen:
  • Primitive Datentypen sind nicht eindeutig; die Ausdruckskraft des Wertes ist nicht gegeben.
  • Ungültige Zustände entstehen durch invalide Werte.
  • Logik muss zwangsweise in weiteren Klassen umgesetzt werden, was oft zu Redundanzen führt.
  • Die Wartbarkeit des Codes leidet immens.
  • Massiv erhöhte Aufwände bei der Refaktorisierung, Erweiterung und/oder Fehlersuche sowie beim Testen des Codes.
Das einfachste Beispiel dafür, dass primitive Datentypen den Anforderungen von Werten in dieser Form nicht gerecht werden, ist, dass Werte bei der Parametrisierung sehr einfach vertauscht werden können. Dies führt oft zu großen Programmierfehlern, die insbesondere bei der Nutzung, der Wartbarkeit sowie späteren Migrationen sehr hohe Aufwände und Folgefehler verursachen.Als Beispiel eine Klasse, die einen Benutzer repräsentiert:

public class Problemstellung
{
  // Vereinfachtes Benutzer-Objekt mit
  // Eigenschaften als primitive Datentypen
  public class User(int userId, string name, int age, 
      int height, int weight)
  {
    public int UserId { getset; } = userId;
    public string Name { getset; } = name;
    public int Age { getset; } = age;
    public int Height { getset; } = height;
    public int Weight { getset; } = weight;
  } 
Die Benutzer-Klasse User besteht aus vier Eigenschaften, deklariert mit primitiven Datentypen. Vier dieser Datentypen sind als Integer definiert, eine Eigenschaft – der Benutzer­name – als String.Des Weiteren verfügt diese Klasse über einen Primary Constructor, eine Syntax-Schreibweise, die seit C# 12 zur Verfügung steht [2]. Eine Instanz dieser Klasse kann demnach nur erstellt werden, wenn alle Parameter gegeben sind.Die Instanzierung dieser Klasse und damit die Übergabe der Parameter birgt nun das Risiko, dass Parameter vertauscht werden, da deren Typ identisch ist:

public void Code()
{
  UserId userId  = new(1);
  UserName name  = new("Batman");
  Age age        = new(47);
  Centimeters height = new(183);
  Kilograms weight   = new(84);
  // Die Parameter "weight" und "height" sind 
  // vertauscht.
  // Durch die verschiedenen Typen gibt es vom Compiler 
  // eine Fehlermeldung
  User user = new User(userId, name, age, weight, 
    height);
} 
Aus Compiler-Sicht wurden alle Parameter und deren Werte korrekt übergeben – doch aus Logiksicht wurden die Parameter weight und height vertauscht.Oft fällt dieser Fehler während der Entwicklung nicht auf, sondern erst während der Nutzung der Software – oder wenn für jede Parameterübergabe entsprechende Unit-Tests geschrieben werden, was jedoch den Aufwand des Testens erhöht. Eine direkte Compiler-Unterstützung ist an dieser Stelle nicht möglich: Die Datentypen wurden schließlich korrekt behandelt.Ein weiteres Problem dieses Vorgehens ist, dass die Bedeutung der Parameter für den Entwickler ohne weitere Dokumentation nicht eindeutig gegeben ist. Der Parameter name sagt nicht aus, ob dies der Vorname ist, der Nachname oder der vollständige Name – oder eben der Benutzername. Hinzu kommt die Identifikation von Werten, denn allein an der Bezeichnung height beziehungsweise weight wird nicht klar, welche Einheiten – eben Zentimeter beziehungsweise Kilogramm – die Werte besitzen.

Das Konzept der Value Objects

Die konzeptionell einfachste und durchweg sicherste Lösung, solche potenziellen Fehler implizit zu vermeiden und auf diese Weise Folgeaufwände zu verhindern, ist, dass eigene Typen definiert werden; in diesem Fall sogenannte Value Objects [3]. Dabei handelt es sich um Typen, die sich durch drei Eigenschaften maßgeblich von anderen Typ-Implementierungen unterscheiden:
  • Sie repräsentieren einen eindeutigen Wert, der jedoch auch aus mehreren Werten kombiniert sein kann.
  • Ihr beinhalteter Wert ist immutable, der zugewiesene Wert ist nach der Instanzierung also unveränderlich.
  • Sie besitzen keine direkte Identität, also zum Beispiel keine ID.
Im Fall der Benutzerklasse werden daher nun die Werte als eigenständige Typen durch eigenständige Records umgesetzt. Alle diese fünf neu erstellten Records stellen Value Objects dar, wobei es konzeptionell denkbar wäre, dass ein Value Object nicht nur eine Eigenschaft besitzt, sondern – wie beispielsweise bei Adressen – mehrere Eigenschaften Teil dieser Klasse sein könnten.Value Objects dürfen zudem auch andere Value Objects verwenden.

// Value Objects
public record UserId(int Value);
public record class UserName(string Value);
public record class Age(int Value);
public record class Centimeters(int Value);
public record class Kilograms(int Value); 
Das Benutzerobjekt verwendet nun keine primitiven Datentypen für die Eigenschaften mehr, sondern die neu definierten Value Objects:

// Definition des Benutzers mit Value Objects
public class User(UserId userId, UserName name, Age age, 
    Centimeters height, Kilograms weight)
{
  public UserId UserId { getset; } = userId;
  public UserName Name { getset; } = name;
  public Age Age { getset; } = age;
  public Centimeters Height { getset; } = height;
  public Kilograms Weight { getset; } = weight;
} 
Werden Parameter beziehungsweise deren Werte bei der Instanzierung vertauscht, kann der Compiler dies nun erkennen und quittiert es, wie in Bild 1 gezeigt, mit entsprechender roter Unterringelung sowie der obligatorischen CS1503-Fehlermeldung, dass die Typen nicht korrekt übergeben wurden.
Compiler-Unterstützung bei vertauschten Value Objects (Bild 1) © Autor
Value Objects sind also ein sehr bequemes und zugleich sehr effizientes Mittel, um einfache Flüchtigkeitsfehler zu verhindern und Werten einen eindeutigen Bezeichner zu geben.

Der Overhead von Value Objects

Value Objects als eigener, starker Typ der Klassendefinition haben einen bedeutenden Nachteil: Die Instanzierung erzeugt ein Objekt zur Laufzeit und damit entsprechend auch einen Overhead bei der Allokation von Speicher – wie bei grundlegend jeder Klasse. Bei besonders Performance-relevanten Operationen, bei denen Hunderttausende von Objekten innerhalb kürzester Zeit erzeugt werden, kann sich dies unter Umständen nachteilig auswirken.Die Umsetzung durch Strukturen (struct) statt Klassen ­wäre hier eine mögliche Abhilfe, jedoch werden Strukturen nicht immer unterstützt – zum Beispiel beim Einsatz mit dem En­tity Framework.

Besserer Code durch StrongOf

Abhilfe schafft hier die auf GitHub veröffentliche Open-­Source-Bibliothek StrongOf [4], die den Umgang mit Value Objects vereinfacht und standardisiert. Jede in StrongOf zur Verfügung stehende Klasse – sowohl für die Definition von eigenen Typen und statischen Klassen als auch von Erweiterungsklassen – verfolgt dabei das grundlegende Ziel, den Umgang mit eigenen Typen beziehungsweise Value Objects zu vereinfachen. Denn der Hauptgrund, warum Primitive Obsession in C#-Quellcode so verbreitet ist, ist vermutlich, dass jeder eigene Typ entsprechend eigene Methoden bräuchte – zum Beispiel Vergleichsmethoden –, um mit ihnen ähnlich bequem umgehen zu können, wie es der Umgang mit primitiven Datentypen bereits ist.StrongOf kann direkt über die .NET-Paketverwaltung NuGet installiert werden, zum Beispiel via Visual Studio (Bild 2). Neben der Hauptbibliothek StrongOf existieren derzeit drei weitere Pakete, die individuelle Szenarien fokussieren: der Umgang mit System.Text.Json für die JSON-Serialisierung, Integration in das ASP.NET-Core-Modelbinding-System, sowie die Verwendung zusammen mit der sehr beliebten Bi­bliothek FluentValidation.
StrongOf-Pakete im Visual Studio NuGet-Explorer (Bild 2) © Autor
Das Hauptpaket StrongOf liefert verschiedene Basisklassen für die Abstraktion eigener Value Objects der primitiven Datentypen, zum Beispiel StrongGuid für die Abstraktion der Benutzer-ID.

// StrongOf
using System.Diagnostics.CodeAnalysis;
using StrongOf;
public class StrongOfSamples
{
  public class UserId(Guid Value) : 
    StrongGuid(Value);
  public class Kilograms(int Value) : 
    StrongInt32(Value);
  public class Age(int Value) : 
    StrongInt32(Value);
  public class UserName(string Value) : 
    StrongString(Value); 
StrongOf setzt dabei auf das Prinzip der Vererbung, wobei der definierte Typ – zum Beispiel UserName – als generischer Parameter an den Basistyp – hier StrongString<T> – übergeben wird. StrongString besitzt hier als Basis eine Vielzahl von Methoden, mit denen man beim Umgang mit einem String vertraut ist; dazu gehören Vergleichsmethoden wie Equals, aber auch Manipulationsmethoden wie Trim, ToLower, ToUpper und Co. Alle Methoden der Basisklasse sind damit implizit in der eigenen Typdefinition verfügbar, wie man es vom darunterliegenden Typ – hier string – gewohnt ist.

public void MethodSample()
{
  UserName userName = new UserName("Batman");
  UserName lowerUserName = 
    userName.ToLower(); // "batman"
} 
Entsprechende Standardmethoden der jeweiligen primitiven Datentypen werden auch für die weiteren Strong-Typen StrongInt32, StrongInt64, StrongDateTime et cetera zur Verfügung gestellt, sodass die Nutzung möglichst standardisiert, ohne zusätzlichen Aufwand und sehr bequem ist.Alle mit StrongOf definierten Klassen agieren zur Runtime als echte Value Objects; der Vergleich zweier Instanzen erfolgt deshalb ausschließlich auf dem Wert und nicht auf der Referenz.Auch hier ist der Mechanismus die Vererbung: Alle Vergleichsanforderungen prüfen hierbei ausschließlich die Werte – sogar gegen primitive Werte. Das bedeutet, dass sich ein StrongString gegen einen string vergleichen lässt, und ebenso ein Strong­Int32 gegen ein Integer oder Double, wie es auch direkt mit den primitiven Datentypen der Fall wäre. Dies vermeidet das zusätzliche Erstellen von Instanzen für Vergleichsoperationen.

public void CompareSamples()
{
  UserName userName1 = new UserName("Batman");
  string userName2 = "Batman";
  bool isSame = userName1 == userName2; // true
} 
Durch den generischen Vererbungsansatz der Typdefinitionen kann StrongOf zusätzlich statische Klassen sowie Erweiterungsklassen anbieten. Diese Klassen erleichtern den Umgang, sodass kein beziehungsweise nur wenig weiterer Code-Aufwand investiert werden muss, um mit eigenen Value Objects zu arbeiten.Hat man bisher einen String mithilfe von string.IsNullOrEmpty auf Inhalte geprüft, so steht mit StrongOf Strong.IsNullOrEmpty für alle String-basierten Typdefinitionen zur Verfügung:

public void StaticSamples()
{
  // statt string.IsNullOrEmpty()
  UserName? userName = null;
  if (Strong.IsNullOrEmpty(userName))
  {
    userName = new UserName("Batman");
  }
} 

Validierung von Value Objects

Ein wichtiger Aspekt des Value-Object-Konzepts ist es, dass diese nur instanziert werden sollten, wenn der Wert gültig ist. StrongOf verzichtet von Haus aus bewusst auf die Validierung der Werte, um die Effizienz der Instanzierung nicht negativ zu beeinflussen. Denn in der realen Welt müssen zum Beispiel aus historischen Gründen Instanzen eines Value Objects erstellt werden, obwohl der Wert nach aktueller Definition gar nicht mehr gültig ist. Ebenso sollten stets Instanzen von Value Objects erzeugt werden können, die bereits zum Beispiel in der Datenbank existieren.Ist eine Validierung aktiv gewünscht, empfiehlt sich die Verwendung des in .NET weithin geläufigen Try-Patterns; eine statische Methode, die einen booleschen Wert zurückliefert, ob die Instanz anhand eines Wertes erzeugt wurde oder nicht:

public class Validierung
{
  public class Age(int Value) : StrongInt32(Value)
  {
    public static bool TryCreate(
        int value, [NotNullWhen(true)] out Age? age)
    {
      // beispielhafte Validierung
      if (value is > -1 and < 100)
      {
        age = new Age(value);
        return true;
      }
      age = null;
      return false;
    }
  } 
Dies ermöglicht die Verwendung des eigenen Value Objects sowohl mit einer Validierung als auch ohne diese. Sollte ein komplexes Validierungsergebnis gewünscht sein, kann auch dies als Value Object umgesetzt werden, zum Beispiel mit einer Eigenschaft, ob die Erzeugung selbst erfolgreich war, und einer zweiten Eigenschaft mit einem detaillierteren Fehler, wenn die Erzeugung durch den Wert nicht möglich war.

public void Sample()
{ 

StrongOf mit dem Entity Framework

Beim Einsatz von Entity Framework oder anderen Bibliotheken sowie bei der Serialisierung zu JSON, XML und Co. muss beachtet werden, dass Value Objects zur Runtime stets einen eigenen Typ darstellen. Dies bedeutet auch, dass in den meisten Fällen ein Konverter deklariert werden muss, damit bei der Übersetzung der jeweilige Serializer weiß, was er tun muss.Das Entity Framework bietet hier die Möglichkeit, sogenannte ValueConverter [5] zu definieren, sodass das Value Object beim Schreiben in die Datenbank in einen primitiven Datentyp – die UserId zu Integer –, mit dem die Datenbank umgehen kann, übersetzt wird beziehungsweise beim Lesen von einem primitiven Typ der Datenbank in das Value Object – der Integer zu UserId – instanziert werden kann.

namespace EntityFrameworkSample
{
  using Microsoft.EntityFrameworkCore.Storage
    .ValueConversion;
  using StrongOf;
  public class UserIdValueConverter : 
      ValueConverter
  {
    public UserIdValueConverter(
      ConverterMappingHints? mappingHints = null)
      : base(id => id.Value, value => new(value), 
      mappingHints) { }
  } 
An dieser Stelle ist zu beachten, dass das Entity Framework ausschließlich mit Referenz- und nicht mit Wertvergleichen arbeitet, sodass mit dem Entity Framework nur Klassen und keine Records verwendet werden können [6]. Value Objects mit Wertvergleichen können daher nur für Einzelwerte wie die Abstraktion einer ID – hier UserId – verwendet werden; nicht aber für Relationen beziehungsweise Navigation Properties.

Die Performance von Value Objects

Unweigerlich taucht die Frage auf, wie es um die Performance steht. Denn jeder eigene Typ geht, wie beschrieben, mit einem gewissen Overhead einher, der durch die Instanzierung zustande kommt. Einige mit StrongOf vergleichbare Bibliotheken setzen hierbei auf eine generische Instanzerzeugung; sie verwenden also Varianten wie Activator.CreateInstance() oder Expression.New(), um eine Instanz zu erzeugen.StrongOf bietet zwar mit einer statischen From-Methode eine generische Erzeugung durch Expression.New() an, die für gewisse Szenarien notwendig ist, sieht aber für die ­generelle Nutzung die Instanzierung der eigenen Typen durch den Konstruktor vor. Die Konstruktor-Varianten sind durchweg mindestens um den Faktor drei effizienter und performanter (Bild 3).
Instanzerzeugungs-Benchmark: Konstruktor versus Expression.New (Bild 3) © Autor

StrongOf im Vergleich

Neben StrongOf existieren weitere Bibliotheken wie Vogen [7] oder StronglyTypedId [8]; doch von diesen unterscheidet sich StrongOf deutlich:
  • StrongOf setzt auf das Prinzip der Vererbung statt auf Code-Generatoren, um generische Erweiterungen auf einfache Art und Weise zu ermöglichen.
  • StrongOf bietet durch die Instanzierung über den Konstruktor eine effizientere Umsetzung an.
  • StrongOf bietet bewusst keine direkt eingebauten Validierungsmöglichkeiten, um die Runtime-Effizienz hoch zu halten. Diese müssen bei Bedarf selbst umgesetzt werden (siehe Validierungsbeispiel).
  • StrongOf bietet eine eingebaute Unterstützung für alle aktuell existierenden primitiven Datentypen.
  • Eigene StrongOf-Ableitungen mit eigenen komplexen Typen sind einfach umsetzbar.
  • StrongOf ist leichtgewichtig und besitzt keine eingebetteten Abhängigkeiten zu anderen Bibliotheken.

Extension Types mit C#

Die einfache Vermeidung von Primitive Obsession, wie sie ­beispielsweise in F# mit Type Abbreviations [9] möglich ist, ist ­eines der am längsten ersehnten Sprachfeatures von C#. Der früheste aktuell auffindbare Feature Request stammt von 2017 [10] und wurde immer wieder in der Community heiß diskutiert, auf GitHub ebenso wie auf Reddit, aber auch auf Twitter.Mads Torgersen – Lead Designer für C# bei Microsoft – hat es sich nun nicht nehmen lassen, auf der Microsoft Build 2024 im Rahmen der Session „What’s new in C#13“ [11] selbst die wohl größte Neuerung der letzten Jahre vorzustellen: die C# Extension Types (siehe auch [12]). Sie wurden das erste Mal als Sprachfeature – hier noch als simple Bezeichnung „Extensions“ – im November 2021 genannt [13] und seit Dezember 2021 in der Community offen diskutiert [14] [15].Extension Types sind nun genau das, was die Community sich sehr lange gewünscht hat; sie können jedoch viel mehr als bisher bekannte Lösungen wie Type Abbreviations. Mit ihnen wird es zukünftig möglich, einen eigenen Typ im Sinne einer echten Typ-Erweiterung deklarieren zu können.Extension Types werden mit zwei verschiedenen Schlüsselwörtern kommen: implicit beziehungsweise explicit. Die impliziten Deklarationen dienen dazu, bestehende Typen zu erweitern: Sie verhalten sich also ähnlich wie die bereits bekannten Extension Methods [16]. Die jedoch deutlich wichtigere Neuerung sind die explicit extensions: Mit ihnen lassen sich eigene Typen als „leichtgewichtiger Typ“ deklarieren. Hierzu wird ein Typ mit einer neuen Schreibweise deklariert:

public class ExtensionTypes
{
  // Es gibt noch keine Compilerunterstützung, daher   
  // werden Schlüsselwörter rot als Fehler gekennzeichnet.
  public explicit extension UserId for Guid
  {
    public static UserId New() => UserId.NewGuid();
  } 
Der Extension Type – hier UserId – erweitert den primitiven Datentyp Guid; besitzt also die exakt gleichen Runtime-Eigenschaften und erhält auch alle Methoden, die der Typ Guid besitzt. Zur Compile-Time sind Guid und UserId jedoch unterschiedliche Typen, sodass eine entsprechende Compiler-Unterstützung gewährleistet wird und die Probleme der Primitive Obsession vollständig gelöst sind.Diese eigenen Typen können nun wie gewohnt – wie alle anderen Typen (Klassen, Strukturen, Werte et cetera) – behandelt und in eigenen Value Objects verwendet werden:

public class User
{
  public UserId Id { getset; }
} 
Extension Types sollen sich dabei nicht nur auf primitive Datentypen beschränken; vielmehr wird es – nach aktuellem Designstand – zukünftig möglich sein, jede Art von Typen zu erweitern, was immense Auswirkungen auf bisherige Implementierungen in C# hat. Statt einer Vererbung wie

// record Schreibweise
public record class Tier(string Name);
public record class Hund(string Name) : Tier(Name);
public record class Katze(string Name) : Tier(Name);
public record class Maus(string Name) : Tier(Name); 
werden – mit allen Vor- und Nachteilen – diese Situationen auch mit Extension Types umsetzbar sein:

public record class Tier(string Name);
public explicit extension Hund  for Tier;
public explicit extension Katze for Tier;
public explicit extension Maus  for Tier; 
Extension Types sind also womöglich wirklich die disruptiv­ste Neuerung – im positiven Sinne – in C#, die wir in den letzten Jahren erhalten haben. Eigentlich angekündigt für den Release von .NET 9 zusammen mit C# 13, werden Extension Types nun voraussichtlich erst mit C# 14 und den ersten Previews von .NET 10 [17] erscheinen.
Projektdateien herunterladen

Fussnoten

  1. [1] Mark Seemann, Design Smell: Primitive Obsession, http://www.dotnetpro.de/SL2410StrongOf1
  2. [2] Microsoft Learn, Primary Constructor, http://www.dotnetpro.de/SL2410StrongOf2
  3. [3] Microsoft Learn, Value Objects, http://www.dotnetpro.de/SL2410StrongOf3
  4. [4] StrongOf auf GitHub, http://www.dotnetpro.de/SL2410StrongOf4
  5. [5] Microsoft Learn, ValueConverter, http://www.dotnetpro.de/SL2410StrongOf5
  6. [6] Microsoft Learn, When to use records, http://www.dotnetpro.de/SL2410StrongOf6
  7. [7] Vogen auf GitHub, http://www.dotnetpro.de/SL2410StrongOf7
  8. [8] StronglyTypedId auf GitHub, http://www.dotnetpro.de/SL2410StrongOf8
  9. [9] Microsoft Learn, Type Abbreviations, http://www.dotnetpro.de/SL2410StrongOf9
  10. Proposal 410, Type aliases/abbreviations/newtype, http://www.dotnetpro.de/SL2410StrongOf10
  11. Dustin Campbell, Mads Torgersen, What’s new in C# 13, http://www.dotnetpro.de/SL2410StrongOf11
  12. Microsoft Learn, What’s new in C# 13, http://www.dotnetpro.de/SL2410StrongOf12
  13. Proposal 5497, Extensions, http://www.dotnetpro.de/SL2410StrongOf13
  14. Proposal 5496, Roles and extensions, http://www.dotnetpro.de/SL2410StrongOf14
  15. C# Language Design Meeting for December 11th, 2023, http://www.dotnetpro.de/SL2410StrongOf15
  16. Microsoft Learn, Extension Methods, http://www.dotnetpro.de/SL2410StrongOf16
  17. Kathleen Dollard, C# 13: Explore the latest preview ­features, http://www.dotnetpro.de/SL2410StrongOf17

Neueste Beiträge

DWX Tag 2: Die Devs sind immer schuld - Konferenz
DWX, zweiter Konferenztag: Opening mit der Keynote von Dr. Carola Lilienthal, Interviews und Verlosungen.
3 Minuten
2. Jul 2025
Letzter Tag, große Themen: Git-Deep-Dive, AI-Diskussionen und fette Preise - DWX 2025
Am letzten Konferenztag wurde nicht etwa gemütlich ausgerollt – im Gegenteil. Die Agenda hatte es noch einmal in sich: ein technischer Deep-Dive, ein zukunftsweisendes Panel und ein paar ziemlich attraktive Gewinne. Hier kommt der Rückblick.
4 Minuten
4. Jul 2025
DWX Daily: The Grand Opening - Konferenz
DWX, erster Konferenztag: Zum ersten Mal findet die DWX in Mannheim statt - in neuem Look and Feel. Das wurde gebührend gefeiert.
3 Minuten
1. Jul 2025
Miscellaneous

Das könnte Dich auch interessieren

C# 7: Der richtige Einsatz von ref - Programmiersprache
Die neue Version von C# unterstützt nun Referenzen mittels des Schlüsselwortes ref. Damit lassen sich etwa schnelle Zugriffe auf Arrays programmieren, die noch dazu sicher sind.
3 Minuten
25. Apr 2017
LogTape: Logging-Ansatz für JavaScript-Bibliotheken
LogTape bietet Logging in JavaScript-Bibliotheken.
2 Minuten
23. Jun 2025
C#: async und await im Detail erklärt - Patrick Smacchia
.NET bietet Hunderte von asynchronen Methoden für alle Arten von E/A-Aufgaben, einschließlich Netzwerkzugriff, Datenbankzugriff, JSON-XML-Binärdateizugriff, Datenkomprimierung und mehr. Patrick Smacchia erklärt das Konzept ausführlich anhand von Beispielen.
2 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige