Kategorie: Softwareentwicklung

Mehrdimensionale und verzweigte Felder in C# 👍 👎

Eindimensionale Felder in C# sind schnell eingeführt und verwendet, dazu hatte ich auch schon einmal etwas im Zusammenhang mit PHP geschrieben. Bei den weiteren Varianten des Arrays in C# kann man grundsätzlich zwischen zwei Möglichkeiten unterscheiden: Mehrdimensionale und Verzweigte ("Array von Arrays"). Wir möchten uns daher beide einmal näher ansehen und beginnen zuerst mit dem mehrdimensionalen Feld:
Mehrdimensionales Feld
0102030405
int[,] matrix = new int[,] {    {11,12,13,14,15},    {21,22,23,24,25},    {31,32,33,34,35}};
Während es sich bei eindimensionalen Feldern mathematisch betrachtet um Vektoren handelt, handelt es sich hierbei um eine m*n-Matrix (mit 3 Zeilen und 5 Spalten); analog dazu wären auch dreidimensionale Gebilde etc. möglich. Wichtig zu erkennen ist an dieser Stelle, dass es sich tatsächlich um eine rechteckige Anordnung (nicht jedoch zwangsläufig um eine quadratische) handelt, d. h. jede Zeile besitzt gleich viele Spalten.

Wir können anschließend wie folgt damit arbeiten:
Elemente durchlaufen
0102030405060708091011121314151617181920212223242526272829
  // Verschachtelte Schleifenfor(int m = 0; m < 3; m++) {      // Zeilen    for(int n = 0; n < 5; n++) {  // Spalten        /**         * matrix[m, n] enthält das         * entsprechende Element.        **/    }}
// Einfache Schleifefor(int i = 0; i < matrix.Length; i++) { // matrix.Length = (3 * 5) /** * matrix[i / 5, i % 5] enthält * das entsprechende Element. * * C++-Entwickler mögen an dieser Stelle * an *(*matrix + i) denken, was man bei * C# jedoch nur "unsafe" bekommt. **/}
// FOREACH-Schleifeforeach(int x in matrix) { /** * "x" enthält nun das * entsprechende Element. **/}
Eine weitere Möglichkeit sind nun die sog. verzweigten Felder:
Verzweigtes Feld
0102030405
int[][] data = new int[][] {    new int[] {11,12,13},    new int[] {21,22,23,24},    new int[] {31,32,33,34,35}};
Bereits die Initialisierung verdeutlicht sehr gut, dass es sich wie bereits erwähnt um ein Array von Arrays handelt, wobei jeder Eintrag nun ein Feld "beliebiger" Länge sein kann. Beim Durchlaufen – diesmal im Allgemeinen natürlich nur noch mit verschachtelten Schleifen möglich – wird dies noch einmal dadurch betont, dass wir die entsprechende Length-Eigenschaft (auch) für die innere Schleife nutzen können (und werden):
Elemente durchlaufen
01020304050607080910111213141516171819
  // FOR-Schleifenfor(int i = 0; i < data.Length; i++) {    for(int j = 0; j < data[i].Length; j++) {        /**         * data[i][j] enthält das         * entsprechende Element.        **/    }}
// FOREACH-Schleifenforeach(int[] i in data) { foreach(int j in i) { /** * "j" enthält nun das * entsprechende Element. **/ }}
Derartige Felder werden übrigens oft auch "jagged array" genannt. Zuletzt sei noch erwähnt, dass sich beide Formen durchaus auch kombinieren lassen. Weitere Informationen und Beispiele gibt es wie üblich im MSDN.

Alphabetischer Index mit C# und LINQ 👍 👎

Häufig kann es sinnvoll sein, eine Liste nach Anfangsbuchstaben gruppiert darzustellen. Dies kann unter C# in Verbindung mit LINQ sehr einfach und elegant geschehen, wozu wir wie üblich ein kleines Beispiel durchgehen.

Konkret möchten wir Personen nach Anfangsbuchstabe von LastName gruppiert und darin wiederum nach LastName sortiert ausgeben. Auf Grund der Trivialität der Klasse verweise ich auf das im Artikel zu Eigenschaften in C# präsentierte Beispiel und fahre direkt mit der Liste fort:
Liste von Personen
01020304050607080910111213141516171819202122
List<Person> personList = new List<Person>() {    new Person() {        FirstName = "Harald",        LastName = "Müller"    },    new Person() {        FirstName = "Helga",        LastName = "Schmidt"    },    new Person() {        FirstName = "Karl",        LastName = "Schneider"    },    new Person() {        FirstName = "Olaf",        LastName = "Fischer"    },    new Person() {        FirstName = "Saskia",        LastName = "Weber"    }};
Nun führen wir die Gruppierung ganz einfach per GroupBy(…) aus und speichern dies mit den dazugehörigen Listen per ToDictionary(…) direkt in ein Dictionary:
Liste gruppiert abspeichern
010203
Dictionary<char, List<Person>> personStructure = personList    .GroupBy(p => p.LastName[0])  // Gruppierung nach erstem Buchstaben des Nachnamens    .ToDictionary(k => k.Key, v => v.ToList());
Anschließend können wir die Struktur beispielsweise durchlaufen und auf einer Webpräsenz ausgeben:
Struktur durchlaufen
01020304050607080910
foreach(KeyValuePair<char, List<Person>> data in personStructure) {    // "data.Key" enthält den Anfangsbuchstaben
foreach(Person person in data.Value.OrderBy(p => p.LastName)) {
/** * "person" enthält nun jeweils eine zu eben * diesem Anfangsbuchstaben passende Person. **/ }}
Das Ergebnis einer solchen Vorgehensweise lässt sich u. a. in der neuen Ansicht der Bezirkszuordnungen auf meinem Städteportal für Berlin und Hamburg betrachten. Für diese einfache Darstellung kommt übrigens noch ein zusätzlicher kleiner LINQ-"Trick" mit Select(…) zum Einsatz. Für unser Beispiel könnten wir dazu die innere Schleife schlicht durch Folgendes ersetzen und damit Vor- und Nachname per Komma separiert ausgeben:
Einfache Zeichenketten-Konkatenierung
01
String.Join(", ", data.Value.Select(p => p.FirstName + " " + p.LastName));

XML-Dokumentationskommentare in C# 👍 👎

Zwar nutzt sicherlich jeder sehr gerne die von Visual Studio eingeblendeten Kommentare während der Entwicklung mit dem .NET-Framework, bei vielen Projekten herrscht hier jedoch häufig akuter Mangel. Dabei wird einem dies für Standardkommentare besonders einfach gemacht.

Ihr müsst dazu lediglich über eine Klasse oder beliebigen Member davon (z. B. Eigenschaft, Methode) drei Schrägstriche ("///") in Folge schreiben, worauf Visual Studio automatisch einen passenden XML-Kommentarblock erstellt, den ihr vervollständigen könnt. Am besten sehen wir uns ein "fertiges" Beispiel an:
Klasse "Person" mit XML-Dokumentationskommentaren
01020304050607080910111213141516171819202122232425262728293031
/// <summary>/// Stellt eine Person dar./// </summary>public class Person {    /// <summary>    /// Liefert den Vornamen oder legt diesen fest.    /// </summary>    public string FirstName {        get;        set;    }
/// <summary> /// Liefert den Nachnamen oder legt diesen fest. /// </summary> public string LastName { get; set; }

/// <summary> /// Lässt die Person sprechen. /// </summary> /// <param name="textToSay">Text, der gesprochen werden soll.</param> public void Say(string textToSay) { /* * Implementierung für Beispiel nicht notwendig. */ }}
Alle wesentlichen Bestandteile (insbesondere Rückgabewerte und Parameter) lassen sich separat beschreiben, wobei es zusätzlich immer eine Zusammenfassung gibt. Diese Informationen werden bei Verwendung dieser Klasse nun angezeigt, ganz so wie man es von integrierten Typen kennt.

Als besonders nützlich stellt sich dies selbstverständlich dann heraus, wenn die Klasse (beispielsweise als Bestandteil einer Programmbibliothek) weitergegeben und von anderen Entwicklern verwendet werden soll. Hier gibt es jedoch noch eine kleine Hürde zu bewältigen, denn unsere – mehr oder weniger – mühsam erstellten Kommentare überleben den Kompilierungsvorgang nicht. Ihr könnt Visual Studio jedoch anweisen, eine separate XML-Datei hierfür zu erstellen, welche ihr zusammen mit der DLL weitergeben könnt. Ihr findet die entsprechende Einstellung in der Projektkonfiguration unter Erstellen -> XML-Dokumentationsdatei.

Wie üblich gibt es weitere Informationen zur Verwendung im MSDN, insbesondere eine Tag-Übersicht.

Variadische Methoden in C# 👍 👎

Variadische Funktionen sind Funktionen, die eine unbestimmte Anzahl von Argumenten (→ Arität) entgegennehmen können. Das passende Stichwort an dieser Stelle für PHP-Entwickler ist hauptsächlich func_get_args. Selbstverständlich bietet beispielsweise auch Java eine entsprechende Möglichkeit. Wie ihr sicher bereits befürchtet habt, lege ich den Fokus im weiteren Verlauf jedoch auf C#. Smiley: grinning

Als Beispiel soll uns eine Methode dienen, welche uns die Summe einer beliebigen Anzahl an Zahlen liefert. Zu Vergleichszwecken erinnern wir uns zuerst einmal daran, wie dieses Problem "normal" gelöst werden kann. Die Lösung ist selbstverständlich, ein Feld zu übergeben:
Nicht-variadische Funktion zur Problemlösung
01020304050607
  // Methode definierenpublic int GetSum(int[] list) {    return list.Sum();}
// Methode verwendenint sum = GetSum(new int[] { 1, 2, 3 });
Durch eine minimale Anpassung – nämlich indem wir das params-Schlüsselwort vor dem Parameter ergänzen – wird daraus eine variadische Funktion unter Beibehaltung der bisherigen Funktionalität:
Variadische Funktion zur Problemlösung
010203040506070809
  // Methode definierenpublic int GetSum(params int[] list) {    return list.Sum();}
// Methode verwendenint sum = GetSum(1, 2, 3); // – oder, ebenfalls möglich -int sum = GetSum(new int[] { 1, 2, 3 });
Wie leicht zu sehen ist, handelt es sich soweit letztlich um eine syntaktische Variante, welche nicht unbedingt notwendig ist. Bei Methoden, die sehr häufig z. B. zwei oder drei Argumente erwarten, prinzipiell jedoch auch mehr denkbar wären, erscheint mir eine solche Möglichkeit jedoch durchaus vernünftig. Das .NET-Framework setzt u. a. bei den Methoden String.Format(…) und String.Join(…) auf dieses Vorgehen.

Zuletzt gilt es aber noch auf folgende Besonderheiten hinzuweisen:
  • params-Parameter sind generell optional (d. h. GetSum() wäre möglich)
  • params-Parameter können vor sich fest definierte Parameter besitzen, nicht jedoch danach
  • params-Parameter dürfen pro Methode lediglich einmal auftreten

Konsolenausgabe in C# verwenden (Ergänzung) 👍 👎

In meinem Beitrag zur Verwendung der Konsolenausgabe in C# hatte ich aus naheliegenden Gründen eine asynchrone Verarbeitung gewählt. Mich hat nun jedoch die Frage erreicht, wie die komplette Ausgabe auf einmal abzugreifen geht. Auch das ist selbstverständlich kein Problem – man könnte sogar sagen, noch einfacher.

Wir wollen dazu ein Beispiel wählen, welches uns sehr zügig eine vollständige Antwort liefern sollte. Wir rufen dazu die integrierte Hilfe von ipconfig auf. Die grafische Oberfläche besteht nunmehr nur noch aus einer TextBox output und einer Schaltfläche, für die wir die entsprechende Ereignisbehandlung implementieren:
Ereignisbehandlung implementieren
01020304050607080910111213141516
  // Prozessdaten festlegenProcess process = new Process() {    StartInfo = new ProcessStartInfo() {        Arguments = "/?",               // Parameter für Hilfe-Anzeige        CreateNoWindow = true,          // Konsolenanzeige unterbinden        FileName = "ipconfig",          // Programm        RedirectStandardOutput = true,  // Ausgabe umleiten        UseShellExecute = false         // Betriebssystemshell deaktivieren    }};
// Prozess startenprocess.Start();
// Ausgabe verarbeitenoutput.Text = process.StandardOutput.ReadToEnd();
Ihr solltet jedoch in jedem Fall beachten, dass dieses Vorgehen zur Blockierung der Benutzeroberfläche führt, wenn das externe Programm nicht schnell genug liefern kann. Dies ist daher nur begrenzt zu empfehlen.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche