Schlagwort: .NET

Konsolenausgabe in C# verwenden 👍 👎

Manchmal kann es erforderlich sein, in eigenen Projekten auf bestehende Konsolenanwendungen zurückgreifen zu müssen. Meist benötigt man dann jedoch auch Zugriff auf die Ausgabe des Programms, z. B. um diese in einer grafischen Benutzeroberfläche mit Windows Forms – ggf. aufbereitet – darzustellen.

Dies lässt sich ganz einfach mit C# lösen. Als kleines Beispiel zeigen wir dazu die Ausgabe des tracert-Befehls in einer entsprechenden Anwendung an. Diese besteht dabei lediglich aus einer TextBox "targetHost", einer ListBox "output" und einem Button – mit Visual Studio sicherlich kein Problem soweit. Smiley: winking

Jetzt müssen wir lediglich noch die Ereignisbehandlung für einen Klick auf die Schaltfläche implementieren:
Ereignisbehandlung implementieren
0102030405060708091011121314151617181920212223
  // Prozessdaten festlegenProcess process = new Process() {    StartInfo = new ProcessStartInfo() {        Arguments = targetHost.Text,    // Eingabe aus TextBox        CreateNoWindow = true,          // Konsolenanzeige unterbinden        FileName = "tracert",           // Programm        RedirectStandardOutput = true,  // Ausgabe umleiten        UseShellExecute = false         // Betriebssystemshell deaktivieren    }};
// Ereignisbehandlung registrierenprocess.OutputDataReceived += (s, eventArgs) => { if(eventArgs.Data != null) { this.Invoke(new Action(() => { this.output.Items.Add(eventArgs.Data); })); }};
// Prozess startenprocess.Start();process.BeginOutputReadLine();
Nun werden alle Ausgaben des Konsolenprogramms asynchron als neue Zeile zu output hinzugefügt. Durch dieses Vorgehen blockiert unsere Oberfläche nicht, so dass damit problemlos weitergearbeitet werden kann. In meinem Fall sieht der Inhalt der ListBox für coders-online.net beispielsweise wie folgt aus:
Ausgabe
01020304050607
Routenverfolgung zu coders-online.net [217.160.176.125] über maximal 30 Abschnitte:
1 <1 ms <1 ms <1 ms Router [192.168.2.1] ( … gekürzt … ) 9 17 ms 16 ms 17 ms coders-online.net [217.160.176.125]
Ablaufverfolgung beendet.
Beachtet zuletzt bitte ggf. auch noch einmal meinen Beitrag zur Freigabe von Ressourcen in C#. Um die Beispiele nicht aufzublähen verzichte ich meist darauf (wie auch auf eine umfassende Fehler- und Ausnahmebehandlung).

NULL für Werttypen in C# 👍 👎

Oftmals stolpern Entwickler anderer Sprachen darüber, dass Werttypen (z.B. int, double oder bool) in C# null nicht direkt zugewiesen werden kann, was insbesondere bei der Arbeit mit Datenbanken schnell notwendig wird.

Es gibt jedoch mit Hilfe der Nullable-Struktur eine elegante Lösung hierfür. Dazu wird ganz einfach der gewünschte Werttyp als Typparameter von Nullable angegeben:
Werttyp ohne und mit "Nullable"
0102030405060708091011
  // Werttyp ohne "Nullable"bool isValid;
isValid = true; // "true" zuweisenisValid = null; // Fehler: "null" kann nicht zugewiesen werden!
// Werttyp mit "Nullable"Nullable<bool> isValid;

isValid = true; // "true" zuweisenisValid = null; // "null" zuweisen
Außer der Zuweisbarkeit von null fügt Nullable auch noch Funktionalität zu unserem Objekt hinzu, mit der wir prüfen können, ob ein Wert vorhanden (bzw. das Objekt null) ist:
Nullable-Funktionalität
010203040506
if(isValid.HasValue) {  /**   * "isValid.Value" enthält den Wert;   * dieser ist ohne Nullable deklariert.  **/}
Zusätzlich gibt es noch die Methode GetValueOrDefault(…), deren Nutzen selbsterklärend sein dürfte.

Meiner Meinung nach könnte man nun durchaus auf die Idee kommen, dass diese Schreibweise etwas umständlich ist, "nur" um null zuweisen zu dürfen. Das sah Microsoft offensichtlich ähnlich, weswegen es eine äußerst kompakte Alternative gibt, indem man dem entsprechenden Werttyp schlicht ein "?" anhängt:
Werttyp mit "Nullable" (Kurzschreibweise)
01020304
bool? isValid;
isValid = true; // "true" zuweisenisValid = null; // "null" zuweisen
Dies ist identisch zur oben beschriebenen "ausführlichen" Variante und stellt somit lediglich eine syntaktische Bequemlichkeit dar, die ich jedoch sehr gerne verwende und auch anderen empfehle, das so zu halten.

Weitere Informationen zu diesem Thema finden sich selbstverständlich im MSDN.

Zeichenketten-Konkatenierung in C# 👍 👎

Ein leider immer noch häufig vorzufindendes "Problem" in vielen C#-Anwendungen ist die vielen Entwicklern aus Sprachen wie PHP vertraute Verknüpfung von Zeichenketten per entsprechendem Operator ("." in PHP, "+" in C#):
Konkatenierung per Operator
0102030405
string tmp = "";
tmp += "ersteZeichenkette";tmp += "zweiteZeichenkette";tmp += "dritteZeichenkette";
Dies ist unter C# aus Sicht der Performanz besonders problematisch, da Zeichenketten in C# unveränderbar ("immutable") sind und demnach bei jeder solchen Verknüpfung ein neues Objekt erzeugt wird. Die Lösung für dieses Problem ergibt sich jedoch elegant durch Verwendung der StringBuilder-Klasse:
Konkatenierung per StringBuilder
0102030405060708
StringBuilder tmpBuilder = new StringBuilder();{    tmpBuilder.Append("ersteZeichenkette");    tmpBuilder.Append("zweiteZeichenkette");    tmpBuilder.Append("dritteZeichenkette");}
string tmp = tmpBuilder.ToString();
Dies ist bei C# üblicherweise bereits bei unter drei entsprechenden Vorgängen deutlich schneller als per Operator. Bei sehr vielen – jedoch durchaus praxisrelevanten – Vorgängen wird dieser Unterschied schnell so groß, dass mit der Verknüpfung per StringBuilder bis zu zwei Größenordnungen mehr Vorgänge in der gleichen Zeit wie per Operator möglich sind. Zudem wird selbstverständlich deutlich weniger Arbeitsspeicher benötigt. Insbesondere bei umfangreichen Schleifen ergibt sich hier also sehr schnell ein ernstzunehmendes Einsparpotential. Es handelt sich keinesfalls um "Mikrooptimierung" und wird auch von Microsoft selbst betont.

Bei ganz einfachen Verknüpfungen mit zwei Zeichenketten verwende ich durchaus die Operator-Variante, da sie etwas kompakter ist und der Unterschied minimal. Sobald mehrere Verknüpfungen notwendig werden, setze ich für gewöhnlich jedoch bereits auf einen StringBuilder.

Die intern erzeugte Zeichenkette des StringBuilder kann zur weiteren Verwendung wie im Beispiel ersichtlich jederzeit per ToString() abgerufen werden. Darüber hinaus stellt diese Klasse auch noch ein paar komfortable Methoden wie beispielsweise AppendLine(…) zur Verfügung.

Dynamische Typisierung in C# 👍 👎

Im Artikel zur impliziten Typisierung hatte ich bereits auf einen späteren Artikel zum Thema dynamische Typisierung verwiesen, was mit diesem Beitrag nun auch erfolgen soll.

Seit .NET 4.0 enthält das Framework die sog. Dynamic Language Runtime, was sich bei C# auf den ersten Blick durch das neue Schlüsselwort dynamic, die Basisklasse DynamicObject und das ExpandoObject mit entsprechender Funktionalität bemerkbar macht.

Die DLR ermöglicht zwar "auch" interessante Aspekte im Hinblick auf die Integration dynamisch typisierter Programmiersprachen wie beispielsweise Python (→ IronPython) oder Ruby (→ IronRuby) in das .NET-Framework, ich möchte in diesem Beitrag aber dennoch bei den Möglichkeiten in C# selbst bleiben. Ein paar Gedanken meinerseits zur Typisierung im Allgemeinen sollen in einem späteren Beitrag folgen.

Wie vielen Lesern dieses Blogs bekannt sein dürfte, handelt es sich bei C# um eine grundsätzlich statisch typisierte Programmiersprache, d. h. die Typen aller Variablen sind bereits zum Zeitpunkt der Kompilierung bekannt, was nicht zuletzt auch ermöglicht, dass euch Visual Studio hilfreiche Hinweise schon während der Entwicklung geben kann.

Durch die DLR wird von diesem Prinzip (glücklicherweise Smiley: winking_grinning) nicht grundsätzlich abgewichen, jedoch stellt sich euch die Möglichkeit, von diesem Vorgehen explizit abzuweichen, indem ihr als Typ dynamic deklariert. Dadurch wird die statische Typprüfung deaktiviert und auf die Laufzeit verschoben. Das bedeutet, dass ihr einer Variable dieses Typs beliebige – also auch gar nicht existente – Eigenschaften zuweisen oder darauf Methoden aufrufen könnt, im Problemfall jedoch zur Laufzeit eine entsprechende Ausnahme erhaltet.

Wir beginnen – wie üblich – mit einem kleinen Code-Beispiel zur Verdeutlichung:
Dynamische Variablen erzeugen
0102
dynamic a = 123;dynamic b = "123";
Nun können wir relativ "normal" damit arbeiten, beispielsweise die Werte in einer MessageBox ausgeben:
Dynamische Variablen verwenden (1)
0102030405060708
MessageBox.Show(a.ToString());  //: "123"MessageBox.Show(b.ToString());  //: "123"/** * C# konvertiert hier nicht in eine mathematische Addition, * da "b" zur Laufzeit als Zeichenkette interpretiert wurde.**/MessageBox.Show(a + b);  //: "123123"
Soweit hätte das natürlich auch ohne dynamic funktioniert, der interessante Aspekt ist jedoch, dass sich beispielsweise auch folgender Code kompilieren lässt – Visual Studio zeigt euch bei Eingabe des Punktes (→ IntelliSense) nur mehr "(dynamischer Ausdruck) Der Vorgang wird zur Laufzeit aufgelöst." an:
Dynamische Variablen verwenden (2)
0102
MessageBox.Show(b.Replace("2", "4"));  //: "143"MessageBox.Show(a.Replace("2", "4"));  // Ausnahme!
Ohne dynamic hätte euch Visual Studio bereits während der Entwicklung angezeigt, dass Replace(…) für den Datentyp int nicht zur Verfügung steht (bzw. erst gar nicht zur Auswahl angeboten) und die Kompilierung mit einem Fehler abgebrochen.

Im Gegensatz zu var kann sich der Typ unserer Variable nun auch während der Laufzeit ändern:
Änderung des Typs zur Laufzeit
01020304050607
MessageBox.Show(a.GetType().ToString());  //: "System.Int32"MessageBox.Show(a.ToString());  //: "123"
a = "test";

MessageBox.Show(a.GetType().ToString()); //: "System.String"MessageBox.Show(a.ToString()); //: "test"
Dies entspricht schon sehr dem Verhalten, wie man es von Sprachen wie PHP kennt und bringt die entsprechenden Vorteile, aber eben auch gravierende Nachteile mit sich. Ob eine Operation zu Problemen führt, erkennt man nunmehr erst zur Laufzeit – und mit etwas Pech auch nicht sofort, da die ungültige Operation möglicherweise nicht sofort zur Verwendung kommt, mit statischer Typisierung aber dennoch bereits zur Entwicklungszeit von Visual Studio darauf hingewiesen worden wäre, dass dies niemals möglich sein wird.

Wenn das nun neben ein bisschen Bequemlichkeit beim Tippen doch eher Probleme einführt, stellt sich natürlich die berechtigte Frage, wozu man so etwas verwenden sollte. Hauptsächlich handelt es sich tatsächlich um ein Mittel, dynamische Sprachen in das .NET-Framework zu integrieren, welche nun einmal auf diesem Konstrukt basieren. Auch die Verwendung entsprechender Programmbibliotheken, um beispielsweise einfach über XML-Knoten zu navigieren (man denke an dieser Stelle auch an das DOM) sind sicherlich eine praktikable Idee. Für die reine Bequemlichkeit ist implizite Typisierung jedoch definitiv besser geeignet.

Ich würde auf jeden Fall dringend davon abraten, unter C# selbst exzessiven und vor allem unüberlegten Einsatz davon zu machen. Natürlich kann man sich dadurch z. B. gewisse Umstände, die Reflektion mit sich bringt, ersparen, dennoch würde ich hier nach wie vor zu einem sauberen, auf Schnittstellen basierten Modell raten. Dadurch, das alles "irgendwie" aufgerufen werden kann, handelt man sich nicht selten schwer auffindbare und auch lösbare Fehler ein. Insbesondere sollte bedacht werden, dass das .NET-Framework beim Aufruf entsprechender Funktionalität auch immer passende Datentypen erwartet; Folgendes ergibt beispielsweise umgehend einen Laufzeitfehler:
Gefahren bei der Interaktion mit dem .NET-Framework
010203
dynamic c = 5;
MessageBox.Show(c); // Ausnahme!
Das Problem ist nämlich, dass Show(…) eine Zeichenkette erwartet; bei b hätte es auch so funktioniert, c muss jedoch zwingend mit ToString() aufgerufen werden. Mit statischer Typprüfung sind das alles Probleme, die man bereits beim Schreiben des Quelltextes erfährt. Mit dynamischer Typprüfung möglicherweise aber erst nach einigen Stunden oder gar noch längerer Laufzeit, wenn die Stelle zum ersten Mal zur Ausführung kommt.

Da wir nun umfassend auf die potentiellen Gefahren eingegangen sind und erste Beispiele betrachtet haben, möchte ich noch einmal kurz auf DynamicObject und ExpandoObject zurückkommen und dazu jeweils ein kleines Anwendungsbeispiel demonstrieren. Wir beginnen mit dem ganz einfachen ExpandoObject und fügen diesem Eigenschaften und Verhalten hinzu:
ExpandoObject definieren und verwenden
01020304050607080910111213141516
dynamic obj = new ExpandoObject();{      // Eigenschaften setzen    obj.FirstName = "Holger";    obj.LastName = "Stehle";
obj.Age = 24;}
// Verhalten definierenobj.GetName = new Func<string>(() => { // der Rückgabetyp könnte auch "dynamic" lauten return obj.LastName + ", " + obj.FirstName;});
// Name und Alterstring person = obj.GetName() + " (" + obj.Age + ")"; //: "Stehle, Holger (24)"
Als nächstes wollen wir noch einen (kleinen) Blick auf das DynamicObject werfen, von welchem wir erben und dabei spezifisches Verhalten für bestimmte Aktionen festlegen können. Konkret möchten wir den Zugriff auf Eigenschaften unabhängig von Groß-/Kleinschreibung ermöglichen:
DynamicObject erben und verwenden
01020304050607080910111213141516171819202122232425262728293031
  // Klasse ableiten und Verhalten anpassenpublic class MyDynamicObject : DynamicObject {    private Dictionary<string,object> data = new Dictionary<string,object>();

// Eigenschaft abrufen public override bool TryGetMember(GetMemberBinder binder, out object value) { return data.TryGetValue(binder.Name.ToLower(), out value); }
// Eigenschaft setzen public override bool TrySetMember(SetMemberBinder binder, object value) { data[binder.Name.ToLower()] = value;
return true; }}
// Klasse verwendendynamic obj = new MyDynamicObject();{ // Eigenschaften setzen obj.FirstName = "Holger"; obj.lastName = "Stehle";}
string firstName = obj.FirstName; //: "Holger"string firstName = obj.firstName; //: "Holger"string lastName = obj.lastName; //: "Stehle"string lastName = obj.LastName; //: "Stehle"
Mein Fazit: Sicherlich wünscht man sich das eine oder andere Mal während der Entwicklung ein wenig mehr Dynamik, aber dafür bezahlt man einen nicht unerheblichen Preis zum Nachteil der Wartbarkeit. Zur Implementierung von Standardaufgaben sollte das unter C# nicht das erste Mittel der Wahl sein, eher eines der letzten – C# bietet hier für gewöhnlich mächtige und vor allem typsichere Alternativen. Ja, das ist durchaus mit etwas mehr Schreibarbeit verbunden.

Wie üblich hält das MSDN weitere Hinweise zur Verwendung parat.

Darstellung von IP-Adressen 👍 👎

Vorweg sei erwähnt, dass ich mich im weiteren Verlauf des Artikels der Übersicht wegen auf IPv4 beschränken werde. Das vorgestellte Konzept funktioniert prinzipiell jedoch auch bei IPv6 in ähnlicher Form.

Jeder hat bestimmt schon einmal IP-Adressen der Form "127.0.0.1" (lokale Adresse), "192.168.1.1" (private Adresse) oder "217.160.176.125" (öffentliche Adresse) gesehen. Das ist eine für den Menschen bequeme Schreibweise, jedoch nur bedingt zur automatisierten Verarbeitung (z. B. für Vergleiche bei Sortierungen) geeignet.

Eine IPv4-Adresse besteht aus vier Segmenten, die jeweils 8 Bit (≙ 4 * 1 Byte = 32 Bit, bzw. 4 Byte) umfassen. Insofern verwendet eine IPv4-Adresse dieser Schreibweise ("dotted decimal") eine Art Stellenwertsystem wie unser gebräuchliches Dezimalsystem, wobei die einzelnen Stellen jedoch mehrere Ziffern umfassen und daher durch ein Trennzeichen (in diesem Fall ein ".") getrennt werden.

Die Wertigkeit der Stelle einer Zahl unseres Dezimalsystems ergibt sich bekanntermaßen aus dessen Position:
Stellenwertsystem von Dezimalzahlen
01020304050607
  1 =                     1 * 100  5 =                     5 * 100
10 = 1 * 101 + 0 * 100 15 = 1 * 101 + 5 * 100
123 = 1 * 102 + 2 * 101 + 3 * 100
Ähnlich verhält es sich nun bei einer IPv4-Adresse, jedoch mit entsprechend anderen Wertigkeiten:
Stellenwertsystem von IP-Adressen
0102030405
127.  0.  0.  1 = 127 * 224 +   0 * 216 +   0 * 28 +   1 * 20 = 2 130 706 433
192.168. 1. 1 = 192 * 224 + 168 * 216 + 1 * 28 + 1 * 20 = 3 232 235 777
82.165. 40.206 = 82 * 224 + 165 * 216 + 40 * 28 + 206 * 20 = 1 386 555 598
In Anbetracht dieser Tatsache können wir einer IPv4-Adresse also einen ein-eindeutigen ganzzahligen Wert zuordnen, indem wir – analog zu unserem Dezimalsystem – die Summe der einzelnen Stellen bilden. Durch diese Darstellungsweise sind nun beispielsweise sehr einfach "größer"- und "kleiner"-Relationen zu ermitteln.

Die zu dieser Thematik passende Eigenschaft Address der IPAddress-Klasse des .NET-Frameworks ist als veraltet gekennzeichnet. Solltet ihr diese Funktionalität dennoch benötigen, so lässt sich dies selbstverständlich auch schnell selbst schreiben – wobei zu berücksichtigen gilt, dass das nur für IPv4-Adressen in dieser Form verwendet werden kann:
Vorgehensweise zur ganzzahligen Darstellung von IPv4-Adressen
010203040506
IPAddress ipAddress = IPAddress.Parse("217.160.176.125");byte[] segmentList = ipAddress.GetAddressBytes();
long ip2long = Convert.ToInt64((segmentList[0] * Math.Pow(2, 24)) + (segmentList[1] * Math.Pow(2, 16)) + (segmentList[2] * Math.Pow(2, 8)) + segmentList[3]); // – oder auch -long ip2long = (((long) segmentList[0] << 24) | ((long) segmentList[1] << 16) | ((long) segmentList[2] << 8) | (long) segmentList[3]);
PHP bietet hier beispielsweise die Funktion ip2long (und deren Gegenstück long2ip) an.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche