C# und .NET entwickeln sich ständig weiter, und jede neue Version bringt eine Vielzahl von Änderungen und Verbesserungen mit sich. In den letzten Versionen, einschließlich C# 9, .NET 5, C# 10 und .NET 6, wurden zahlreiche neue Features eingeführt, die die Entwicklung effizienter und flexibler gestalten.

Eine der markantesten Neuerungen in C# 9 war die Einführung von record-Typen und init-only Settern. Diese Features bieten eine elegante Möglichkeit, unveränderliche Objekte zu definieren, die während ihrer Erstellung initialisiert, aber danach nicht mehr verändert werden können. Dies erleichtert die Arbeit mit DTOs (Data Transfer Objects) und ähnlichen Datenstrukturen, bei denen Unveränderlichkeit oft eine notwendige Eigenschaft ist. C# 9 führte auch switch-Ausdrücke ein, die eine kompaktere und lesbarere Alternative zu traditionellen switch-Anweisungen darstellen. In Kombination mit den neuen Nullable-Referenztypen lässt sich die Handhabung von Nullwerten in einer stark typisierten Umgebung sicherer und klarer gestalten.

Mit .NET 5 kam die Vereinheitlichung von .NET Framework und .NET Core, was die Plattform stabiler und performanter machte. C# 9 und .NET 5 brachten zudem Verbesserungen bei der Arbeit mit Indizes und Ranges, die den Umgang mit Arrays und Strings vereinfachen, indem sie es Entwicklern ermöglichen, auf Teilbereiche von Datenstrukturen intuitiver zuzugreifen.

C# 10 und .NET 6 führten die Möglichkeit ein, Top-Level-Anweisungen zu verwenden, was besonders in einfachen Programmen die Notwendigkeit von Main()-Methoden reduziert. Dies spart Code und macht ihn übersichtlicher. Des Weiteren wurden Namensräume standardmäßig importiert, was zu weniger Boilerplate-Code führt. Eine weitere bedeutende Neuerung war die Möglichkeit, während der Instanziierung von Objekten sicherzustellen, dass bestimmte Eigenschaften gesetzt werden, wodurch die Erstellung und Verwaltung von Objekten sicherer und weniger fehleranfällig wird.

Ein weiteres Highlight war die Verbesserung des Nullprüfungssystems in Methodensignaturen. In C# 10 kann nun überprüft werden, ob ein übergebener Parameter null ist, ohne explizit eine Nullprüfung durchzuführen, was den Code leserlicher und wartungsfreundlicher macht.

Mit C# 11 und .NET 7 wurden unter anderem "raw string literals" eingeführt, eine neue Syntax für die Arbeit mit mehrzeiligen und nicht bearbeiteten Zeichenketten, die besonders in der Verarbeitung von JSON, SQL-Abfragen oder regulären Ausdrücken von Vorteil ist. Diese Strings sind einfacher zu handhaben, da Escape-Zeichen automatisch berücksichtigt werden. Zusätzlich bietet .NET 7 Unterstützung für generische mathematische Operationen, was insbesondere für leistungsintensive Berechnungen von großem Nutzen ist.

Die Integration von GitHub als Repository für das Buch ermöglicht es Entwicklern, Codebeispiele direkt herunterzuladen und mit dem praktischen Nutzen der Plattform zu interagieren. Hier kann man nicht nur den vollständigen Code einsehen, sondern auch etwaige Fehler oder Ungenauigkeiten im Text melden, wodurch der Lernprozess interaktiver und dynamischer wird.

Was bei all diesen Neuerungen nicht außer Acht gelassen werden sollte, ist die kontinuierliche Praxis und das Experimentieren mit den neuen Features. Nur durch das direkte Anwenden und Erforschen der Funktionen in realen Projekten kann man die volle Leistung und den Nutzen der neuen Versionen von C# und .NET ausschöpfen.

Es ist auch wichtig zu verstehen, dass jede neue Version von C# und .NET nicht nur neue Features bietet, sondern auch bestehende Funktionen optimiert. Dies betrifft sowohl die Performance als auch die Benutzerfreundlichkeit der APIs. Das kontinuierliche Update und die Erweiterung der Plattformen sorgen dafür, dass Entwickler in der Lage sind, leistungsstarke, wartbare und sichere Software zu schreiben.

Wie wählt man das richtige Konsistenzlevel und die optimale Partitionierungsstrategie in Azure Cosmos DB?

In verteilten Datenbanksystemen wie Azure Cosmos DB steht man vor der fundamentalen Herausforderung, zwischen Konsistenz, Verfügbarkeit und Latenz abzuwägen. Cosmos DB bietet eine globale Verteilung mit elastischer Skalierung, die auf Replikation basiert, um niedrige Latenzen und hohe Verfügbarkeit zu gewährleisten. Allerdings bringt diese Architektur inhärente Kompromisse mit sich. Linearizität, also die stärkste Form der Konsistenz, garantiert, dass alle Leseoperationen weltweit unmittelbar die letzten Schreibvorgänge widerspiegeln. Dies geht jedoch auf Kosten der Latenz bei Schreibvorgängen und der Verfügbarkeit, da das System auf die globale Replikation warten muss.

Daher bietet Cosmos DB mehrere Abstufungen von Konsistenz an, die als „relaxierte“ Konsistenz bezeichnet werden, um den unterschiedlichen Anforderungen von Anwendungen gerecht zu werden. Die fünf Stufen reichen von stark bis eventual consistency und ermöglichen eine flexible Anpassung an das spezifische Anwendungsszenario. Dabei variiert der Grad der Konsistenz und die garantierte Reihenfolge der Lese- und Schreiboperationen, was direkte Auswirkungen auf die Systemperformance hat.

Die Auswahl des passenden Konsistenzlevels sollte stets die Anforderungen an Datenaktualität, Latenz und Verfügbarkeit widerspiegeln. Beispielsweise ist für Finanzanwendungen mit kritischen Echtzeitdaten häufig starke oder zumindest bounded staleness Konsistenz erforderlich, während für weniger zeitkritische Anwendungen eventual consistency ausreichend sein kann.

Neben der Konsistenz spielt die Datenmodellierung eine zentrale Rolle. Bei Azure Cosmos DB ist es essenziell, den richtigen Partitionierungsschlüssel zu definieren, da dieser die logische Partitionierung der Daten steuert und damit die Grundlage für Skalierbarkeit und effiziente Abfragen bildet. Physische Partitionen werden vom System verwaltet, aber die logischen Partitionen, basierend auf dem Partitionierungsschlüssel, bestimmen, wie Daten gruppiert und Transaktionen abgewickelt werden.

Die Wahl zwischen Normalisierung und Denormalisierung im Datenmodell beeinflusst ebenfalls die Performance und Skalierbarkeit. Normalisierte Modelle reduzieren Datenredundanz und verbessern die Schreibleistung, führen aber zu höheren Abfragekosten, da häufig mehrere Abfragen notwendig sind, um zusammenhängende Daten zu ermitteln. In Szenarien mit häufigen Updates und komplexen Beziehungen (one-to-many, many-to-many) empfiehlt sich daher Normalisierung und Referenzierung. Umgekehrt ist bei häufigen Lesezugriffen auf stark zusammenhängende Daten eine Denormalisierung sinnvoll, um Leseoperationen zu beschleunigen.

Durchsatz in Cosmos DB wird in Request Units (RU/s) gemessen, wobei jede Operation unterschiedlich viele RUs beansprucht, abhängig von Größe, Komplexität der Abfragen und Konsistenzniveau. Ein präzises Abschätzen des benötigten Durchsatzes ist unerlässlich, da eine Unterschreitung zu Rate Limiting und damit zu Verzögerungen führt. Gleichzeitig verursacht ein zu hoher Durchsatz unnötige Kosten. Die Skalierung des Durchsatzes erfolgt flexibel, kann aber nicht spontan unterhalb der Mindestgrenze liegen.

Ein weiterer kritischer Punkt ist die Indexierung: Vollständige Indexierung aller Eigenschaften erhöht zwar die Abfragegeschwindigkeit, kostet aber mehr RUs und Speicherplatz. Daher ist eine gezielte Auswahl der zu indexierenden Felder empfehlenswert, um ein ausgewogenes Verhältnis zwischen Kosten und Leistung zu erzielen.

Zusätzlich ist es wichtig, die verschiedenen Hierarchieebenen in Cosmos DB zu verstehen: vom Account über Datenbanken, Container bis hin zu Items. Jede Ebene hat eigene Skalierungs- und Verwaltungsoptionen. Die Partitionierung auf Container-Ebene ist entscheidend für Skalierbarkeit, da Partitionen als Grundlage für Verteilung und parallele Verarbeitung der Daten dienen.

Es ist außerdem relevant, den Kontext der Anwendung genau zu analysieren, um zwischen den Strategien der Datenmodellierung, Konsistenzlevels und Partitionierung zu balancieren. Die Komplexität der Programmierung steigt bei Verwendung schwächerer Konsistenzmodelle, da Entwickler mit temporären Inkonsistenzen umgehen müssen.

Der Umgang mit häufig aktualisierten Daten, wie etwa bei Börsenkursen, unterscheidet sich grundlegend von langfristig gespeicherten Informationen. In der Praxis kann eine hybride Strategie sinnvoll sein: temporär hochfrequent aktualisierte Daten referenzieren, während weniger volatile Daten eingebettet werden.

Das Verständnis dieser Prinzipien ist für die effiziente Nutzung von Azure Cosmos DB unerlässlich, da nur so eine optimale Balance zwischen Kosten, Leistung, Skalierbarkeit und Datenkonsistenz erreicht werden kann.

Wie man mit Datums- und Zeitwerten in der Programmierung arbeitet und die Globalisierung berücksichtigt

Das Arbeiten mit Datum und Uhrzeit in der Programmierung ist eine der wichtigsten, aber auch komplexesten Aufgaben, insbesondere wenn es um die internationale Nutzung geht. In der Entwicklung von Software, die global eingesetzt werden soll, müssen Entwickler sicherstellen, dass Datum und Uhrzeit korrekt formatiert, interpretiert und berechnet werden, unabhängig von der Kultur und den regionalen Einstellungen des Benutzers. Diese Herausforderung betrifft nicht nur die Ausgabe von Zeitstempeln, sondern auch die Umrechnung von Datumsformaten und die Berechnung von Zeitdifferenzen. In dieser Diskussion werfen wir einen Blick auf verschiedene Aspekte der Datums- und Zeitbehandlung in .NET, einschließlich der kulturellen Anpassung und der neuen Funktionen in .NET 7.

Die Standardformate für Datum und Uhrzeit hängen von der aktuellen Kultur des Systems ab. Zum Beispiel, während im Vereinigten Königreich das Datum als Tag/Monat/Jahr angegeben wird, folgt in den USA das Datumsformat der Reihenfolge Monat/Tag/Jahr. Solche Unterschiede können zu Missverständnissen führen, wenn Datumsangaben in einer falschen Kultur interpretiert werden. Entwickler müssen daher sicherstellen, dass sie die gewünschte Kultur explizit angeben, wenn sie mit Datum und Uhrzeit arbeiten, um Fehlinterpretationen zu vermeiden.

Ein Beispiel für den Umgang mit Datum und Uhrzeit ist die Initialisierung und Darstellung von Datumswerten. In .NET können Sie einfache Datumswerte wie DateTime.MinValue, DateTime.MaxValue oder den Unix-Epoch-Zeitstempel verwenden, um mit besonderen Zeitpunkten zu arbeiten. Diese Werte können je nach Kultur des Systems unterschiedlich formatiert angezeigt werden. Zum Beispiel zeigt DateTime.Now den aktuellen Zeitpunkt im Format der Kultur an, die für das System festgelegt wurde. Entwickler können auch die Kultur explizit setzen, um eine konsistente Darstellung zu erzielen, unabhängig von den Systemeinstellungen.

Die Formatierung von Datum und Uhrzeit in benutzerdefinierten Formaten ist ebenfalls ein wesentlicher Bestandteil des Umgangs mit Zeitwerten. Mit benutzerdefinierten Formatcodes können Entwickler genau angeben, wie ein Datum angezeigt werden soll. So kann beispielsweise der Weihnachtstag 2024 sowohl im Standardformat als auch in einem benutzerdefinierten Format angezeigt werden, um das Datum im gewünschten Stil darzustellen. Die Nutzung solcher Formate, wie etwa dddd, dd MMMM yyyy für den vollständigen Wochentagsnamen, den Tag des Monats und den Monat, ermöglicht eine hohe Flexibilität bei der Darstellung von Datumswerten.

Neben der reinen Darstellung sind auch Berechnungen mit Datum und Uhrzeit von großer Bedeutung. Entwickler können mit Datums- und Zeitwerten einfache mathematische Operationen durchführen, wie das Addieren oder Subtrahieren von Tagen, Monaten oder Jahren. Ein Beispiel wäre die Berechnung von 12 Tagen vor und nach Weihnachten, um festzustellen, an welchem Datum diese Zeitpunkte liegen. Auch die Berechnung von Zeitspannen, wie die Anzahl der Tage oder Stunden bis zu einem bestimmten Ereignis (wie Weihnachten), ist ein häufiger Anwendungsfall.

Ein weiteres neues Feature in .NET 7 betrifft die Unterstützung für Milli-, Mikro- und Nanosekunden. In früheren Versionen von .NET war der kleinste messbare Zeiteinheit der sogenannte "Tick", der 100 Nanosekunden entsprach. Mit der Einführung von Mikrosekunden und Nanosekunden können Entwickler jetzt noch präzisere Zeitstempel erstellen und anzeigen, was besonders für Anwendungen in Bereichen wie der Echtzeitverarbeitung von Daten oder der präzisen Zeitsynchronisation von Systemen nützlich ist. Ein Beispiel hierfür wäre die Erstellung eines Zeitstempels, der auch Mikrosekunden berücksichtigt, um eine noch genauere Zeitdarstellung zu ermöglichen.

Ein weiterer wichtiger Aspekt bei der Arbeit mit Datums- und Zeitwerten ist die Globalisierung. Da verschiedene Kulturen unterschiedliche Formate und Darstellungen für Datumsangaben verwenden, ist es entscheidend, die Kultur des Systems zu berücksichtigen, wenn Daten formatiert oder analysiert werden. In .NET kann die Kultur mithilfe der CultureInfo-Klasse gesteuert werden. Entwickler können sowohl die Eingabeformate für Datum und Uhrzeit als auch die Anzeigeformate auf die spezifische Kultur des Benutzers abstimmen. Dies gewährleistet eine korrekte Interpretation und Darstellung von Datumswerten, die aus verschiedenen Regionen stammen.

Wenn man beispielsweise den Unabhängigkeitstag der Vereinigten Staaten (4. Juli) in verschiedenen Kulturen verarbeitet, muss man darauf achten, wie das Datum eingegeben und interpretiert wird. In Großbritannien würde die Eingabe als "4 July 2024" korrekt sein, während in den USA das Datum als "7/4/2024" dargestellt wird. Das Verständnis dieser Unterschiede und die Verwendung der richtigen Kulturmethoden ist unerlässlich, um Fehler bei der Datumsinterpretation zu vermeiden.

Ein weiterer wichtiger Punkt bei der Arbeit mit Datum und Zeit ist die Zeitzone. Obwohl das Beispiel hier hauptsächlich auf lokale Zeit und Kultur eingeht, ist die Zeitzone ein kritischer Faktor, der häufig vernachlässigt wird. Bei Anwendungen, die global eingesetzt werden, müssen Entwickler sicherstellen, dass die Zeitzone korrekt berücksichtigt wird, insbesondere bei der Berechnung von Zeitdifferenzen oder der Planung von Ereignissen. Auch wenn DateTime.UtcNow eine universelle Zeit liefert, können lokale Unterschiede bei der Zeitzone dazu führen, dass Daten falsch interpretiert oder falsch angezeigt werden, wenn die Zeitzone nicht explizit angegeben wird.

Wie man Ressourcen aus Satelliten-Assemblies in .NET verwendet

In modernen .NET-Anwendungen ist die Handhabung von Internationalisierung und Lokalisierung ein essenzieller Bestandteil, um ein globales Publikum anzusprechen. Dabei spielen Satelliten-Assemblies eine zentrale Rolle. Sie ermöglichen es, Ressourcen wie Texte oder Formate für unterschiedliche Kulturen zu laden, ohne die Hauptanwendung selbst anpassen zu müssen. Für diesen Prozess verwendet .NET bestimmte Standardtypen, wie IStringLocalizer und IStringLocalizerFactory, um auf diese Ressourcen zuzugreifen. Diese Implementierungen werden über den generischen Host von .NET als abhängige Dienste geladen.

Im Folgenden zeigen wir, wie man in einem .NET-Projekt mit Satelliten-Assemblies und Lokalisierung arbeitet. Das Ziel ist, dass die Anwendung je nach Kultur des Benutzers angepasste Ressourcen, wie etwa Texte für Eingabeaufforderungen oder Formatierungen, zur Verfügung stellt.

Zunächst müssen wir sicherstellen, dass das Projekt die richtigen Paketreferenzen für das Arbeiten mit dem generischen Host und der Lokalisierung enthält. In einem Projekt namens WorkingWithCultures fügen wir daher die folgenden Pakete hinzu:

  • Microsoft.Extensions.Localization

  • Microsoft.Extensions.DependencyInjection

Nachdem die Pakete hinzugefügt wurden, kann das Projekt gebaut werden, um die Abhängigkeiten zu installieren. Es folgt der nächste Schritt, bei dem wir im Projektordner einen neuen Ordner namens Resources erstellen, in dem die Lokalisierungsdateien abgelegt werden. In diesem Ordner legen wir eine XML-Datei an, die die Standardressourcen enthält – in der Regel die Ressourcen für die US-amerikanische Kultur (Invariante Sprache). Eine mögliche Ressourcendatei könnte so aussehen:

xml
<root>
<data name="EnterYourName" xml:space="preserve">
<value>Enter your name:</value> </data> <data name="EnterYourDob" xml:space="preserve"> <value>Enter your date of birth:</value> </data>
<data name="EnterYourSalary" xml:space="preserve">
<value>Enter your salary:</value> </data> <data name="PersonDetails" xml:space="preserve"> <value>{0} was born on a {1:dddd}. {0} is {2:N0} minutes old. {0} earns {3:C}.</value> </data> </root>

Diese Datei definiert Ressourcen, die später im Code verwendet werden, um den Benutzer nach seinem Namen, Geburtsdatum und Gehalt zu fragen, und liefert eine Formatierung für die Anzeige von Details zur Person.

Um die Ressourcen im Code zu laden, erstellen wir eine neue Klasse namens PacktResources.cs, die eine Instanz des IStringLocalizer verwendet, um auf die Texte zuzugreifen. Ein Beispiel für die Implementierung könnte wie folgt aussehen:

csharp
using Microsoft.Extensions.Localization;
public class PacktResources { private readonly IStringLocalizer _localizer; public PacktResources(IStringLocalizer localizer) { _localizer = localizer; } public string? GetEnterYourNamePrompt() { string resourceStringName = "EnterYourName"; LocalizedString localizedString = _localizer[resourceStringName]; if (localizedString.ResourceNotFound) { ConsoleColor previousColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Error: resource string \"{resourceStringName}\" not found."); Console.ForegroundColor = previousColor; return $"{localizedString}: "; } return localizedString; } public string? GetEnterYourDobPrompt() => _localizer["EnterYourDob"]; public string? GetEnterYourSalaryPrompt() => _localizer["EnterYourSalary"]; public string? GetPersonDetails(string name, DateTime dob, int minutes, decimal salary) => _localizer["PersonDetails", name, dob, minutes, salary]; }

In der oben gezeigten Klasse wird der Zugriff auf die Ressourcen einfach gemacht. Die GetEnterYourNamePrompt-Methode sucht nach dem entsprechenden String und gibt diesen zurück. Falls der String nicht gefunden wird, wird eine Fehlermeldung ausgegeben. Andere Methoden sind kürzer und greifen direkt auf die Ressource zu.

Im nächsten Schritt müssen wir sicherstellen, dass der generische Host die Lokalisierung und den Dienst PacktResources unterstützt. Dies wird in der Program.cs-Datei konfiguriert:

csharp
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddLocalization(options => { options.ResourcesPath = "Resources"; }); services.AddTransient<PacktResources>(); }) .Build();

Hier konfigurieren wir den Host, um die Lokalisierung zu aktivieren und den Dienst PacktResources bereitzustellen. Dadurch wird sichergestellt, dass der Zugriff auf die lokalisierten Ressourcen korrekt funktioniert.

Nachdem der Host konfiguriert ist, können wir in der Anwendung die Kultur des Benutzers ändern und anschließend die PacktResources-Klasse verwenden, um die entsprechenden Eingabeaufforderungen anzuzeigen. Ein Beispielcode könnte folgendermaßen aussehen:

csharp
PacktResources resources = host.Services.GetRequiredService<PacktResources>();
Console.Write(resources.GetEnterYourNamePrompt()); string? name = Console.ReadLine(); if (string.IsNullOrWhiteSpace(name)) name = "Bob"; Console.Write(resources.GetEnterYourDobPrompt()); string? dobText = Console.ReadLine(); if (string.IsNullOrWhiteSpace(dobText)) { dobText = "1/27/1990"; } Console.Write(resources.GetEnterYourSalaryPrompt()); string? salaryText = Console.ReadLine(); if (string.IsNullOrWhiteSpace(salaryText)) salaryText = "34500"; DateTime dob = DateTime.Parse(dobText); int minutes = (int)DateTime.Today.Subtract(dob).TotalMinutes; decimal salary = decimal.Parse(salaryText); Console.WriteLine(resources.GetPersonDetails(name, dob, minutes, salary));

Nun können wir die Anwendung starten und sehen, wie die Ressourcen geladen werden. Wenn der Benutzer zum Beispiel eine dänische Kultur (da-DK) auswählt, wird die Standardresourcendatei geladen. Für andere Kulturen, wie z.B. Französisch (fr), müssen entsprechende .resx-Dateien vorhanden sein. Für jede Kultur oder Region, die unterstützt wird, müssen wir eine eigene Ressourcendatei erstellen, wie zum Beispiel PacktResources.da.resx oder PacktResources.fr.resx.

Es ist ebenfalls wichtig, dass wir sicherstellen, dass der Benutzer auch tatsächlich in der richtigen Kultur arbeitet, um Missverständnisse bei der Dateneingabe und -anzeige zu vermeiden. Eine robuste Kulturverwaltung und -umschaltung ist für eine professionelle Lokalisierung entscheidend.

Für die vollständige Lokalisierung muss jede Ressourcendatei mit den jeweiligen Texten in der entsprechenden Sprache und ggf. Region ergänzt werden. Eine korrekte Handhabung von Datumsformaten, Währungen und Zahlen ist unerlässlich, um Missverständnisse zu vermeiden und die Benutzererfahrung zu verbessern.