Typisierung 👍 👎

Ein "beliebtes" (Streit-)Thema in der Softwareentwicklung sind die verschiedenen Typsysteme von Programmiersprachen. Grundsätzlich dienen diese hauptsächlich dazu, den Wertebereich von Variablen sinnvoll einzuschränken – idealerweise, um mögliche Fehler zu vermeiden. Vorteilhaft ist meist auch eine deutlich umfassendere Hilfestellung der Entwicklungsumgebung.

Nun gibt es verschiedene Möglichkeiten, ein solches System konkret zu implementieren. Dieser Beitrag soll explizit nicht dazu dienen, das eine System zu verteufeln und das jeweils andere hochzuloben. Dennoch haben die meisten Entwickler – so auch ich – eine gewisse Präferenz, sofern die Situation kein besonderes Vorgehen erfordert und man in der Wahl somit weitestgehend frei ist. Natürlich haben jeweils "beide Welten" ihre Vor- und Nachteile.

Dazu möchte ich einige Gegenüberstellungen mit Beispielen präsentieren, deren Definitionen jedoch nicht unbedingt als allgemeingültige Aussage zu verstehen sind, sondern vielmehr vom Kontext abhängig sein können:

Dynamisch vs. Statisch

Bei der statischen Typprüfung sind die Typen bereits zum Zeitpunkt der Entwicklung bekannt und werden entweder explizit vom Entwickler angegeben, oder aber implizit vom System abgeleitet (mehr dazu im nächsten Abschnitt). Bei der dynamischen Typprüfung findet die Prüfung der Typen erst zur Laufzeit statt.

  • Dynamisch: z. B. C++ (optional), C# (optional), JavaScript, Lua, PHP, Prolog, Python, Ruby, Scheme
    Beispiele in C# und PHP
    01020304050607
      // C#dynamic age = 27;              // zur Laufzeit als "int" erkanntdynamic firstName = "Holger";  // zur Laufzeit als "string" erkannt
    // PHP$age = 27; // zur Laufzeit als "int" erkannt$firstName = "Holger"; // zur Laufzeit als "string" erkannt
  • Statisch: z. B. Ada, C++ (standardmäßig), C# (standardmäßig), Haskell, Java
    Beispiele in C++ und C#
    01020304050607
      // C++int age = 27;                      // Ganzzahlstd::string firstName = "Holger";  // Zeichenkette
    // C#int age = 27; // Ganzzahlstring firstName = "Holger"; // Zeichenkette

Ich bevorzuge hier, sofern die Umstände nicht dagegen sprechen, die statische Typisierung. Für mich überwiegt hier der Vorteil der Typprüfung zur Entwicklungs- bzw. Kompilierzeit. Viele Fehler, die oftmals erst während der Laufzeit auffallen, können so effektiv vermieden werden. Durch Konzepte wie die generische Programmierung kann hier der notwendige Mehraufwand zwar deutlich reduziert werden, dennoch erfordert eine statische Typisierung in der Regel (minimal) mehr Aufwand bei der Entwicklung und kann mitunter umständliche Konstrukte bei sehr dynamischer Datenverarbeitung – beispielsweise im Web-Bereich – erforderlich machen; s. Dynamische Typisierung in C#.

Explizit vs. Implizit

Bei der expliziten Angabe der Typen trägt man als Entwickler Verantwortung dafür, den zum angedachten Inhalt einer Variablen (oder Rückgabe einer Methode) passenden Typen auszuwählen. Bei impliziter Ableitung erledigt dies das Typsystem, indem es den passenden Datentyp zum Inhalt annimmt.

  • Explizit: z. B. Ada, C++, C# (standardmäßig), Java
    Beispiele in C++ und C#
    01020304050607
      // C++int age = 27;                      // Ganzzahlstd::string firstName = "Holger";  // Zeichenkette
    // C#int age = 27; // Ganzzahlstring firstName = "Holger"; // Zeichenkette
  • Implizit: z. B. C# (optional), Haskell, JavaScript, Lua, Prolog, Python, Ruby, PHP, Scheme
    Beispiele in C# und PHP
    01020304050607
      // C#var age = 27;              // Ganzzahlvar firstName = "Holger";  // Zeichenkette
    // PHP$age = 27; // Ganzzahl$firstName = "Holger"; // Zeichenkette

Ich bevorzuge hier, sofern die Umstände nicht dagegen sprechen, die explizite Typangabe. Diese bedeutet üblicherweise zwar etwas mehr Schreibaufwand, erhöht aus meiner Sicht jedoch die Verständlichkeit des Quelltextes, da ich die Bezeichner von Variablen ungerne mit Präfixen oder Suffixen versehe, sondern mich bei der Benennung auf den Inhalt bzw. Zweck konzentrieren möchte. Um eine Kleinigkeit zu testen oder für ansonsten sehr umständliche Konstrukte (vgl. generische Programmierung) empfinde ich implizite Typableitung jedoch durchaus komfortabel. Teilweise kann dies sogar notwendig sein; s. Implizite Typisierung in C#.

Stark vs. Schwach

Dieser Aspekt behandelt im erweiterten Sinne die Typsicherheit des Typsystems. Stark typisierte Programmiersprachen erlauben für gewöhnlich – wenn überhaupt – nur implizite Typumwandlungen, sofern kein Datenverlust auftritt (beispielsweise von einem ganzzahligen Datentyp kleineren Wertebereiches zu einem mit größerem Wertebereich). Schwächer typisierte Programmiersprachen sind hier für gewöhnlich "großzügiger".

  • Stark: z. B. Ada, C++, C#, Haskell, Java, Python, Scheme
    Beispiele in C#
    01020304050607
    int pi = 3.14;    // Kompilierungsfehler!pi = (int) 3.14;  // 3
    int tmp = 27;
    if(tmp) { // Kompilierungsfehler! Debug.WriteLine("klappt!");}
  • Schwach: z. B. JavaScript, Lua, Prolog, Ruby, PHP
    Beispiel in PHP
    0102030405
    $tmp = 27;
    if($tmp) { // implizite Auswertung als "true" echo 'klappt!';} // Ausgabe von "klappt!", da Zahlen ungleich "0" als "true" ausgewertet werden

Ich bevorzuge hier, sofern die Umstände nicht dagegen sprechen, die starke Typisierung. Der Grund ist schlicht der, dass so weitestgehend sichergestellt ist, dass keine unerwarteten oder gar fehlerhaften Konvertierungen vom System vorgenommen werden – sofern ich dem nicht explizit zustimme, wozu die meisten Programmiersprachen entsprechende Typumwandlungen vorsehen. Auch hier ist jedoch zugegebenermaßen üblicherweise ein etwas höherer Aufwand notwendig, als bei eher schwächer typisierten Programmiersprachen.

Meine persönliche Präferenz statischer, expliziter und starker Typsysteme ist tatsächlich auch als eben solche zu verstehen. Ich erkenne durchaus den Vorteil eher "dynamischerer" Sprachen an und nutze diesen ebenfalls in einigen Projekten. Ich habe jedoch für mich festgestellt, mit o. g. Verhalten produktiver zu arbeiten und halte mich daher soweit sinnvoll an solche Sprachen. Insbesondere in C# – was wohl kaum eine Überraschung sein dürfte – finde ich hier eine sehr gute Kombination dieser Aspekte umgesetzt. Wem bisher nur die eine Seite geläufig ist, empfehle ich auf jeden Fall, sich auch einmal eine Sprache mit anderem Typsystem genauer anzusehen.

Insgesamt ist dies eine sehr grobe Einteilung, von der viele Programmiersprachen eine etwas differenziertere vornehmen und tlw. auch Kombinationen erlauben. Dennoch eignet sich dies durchaus für einen ersten Überblick der Möglichkeiten; einige Aspekte hängen auch bis zu einem gewissen Grad voneinander ab. Interessierte sollten sich definitiv beispielsweise einmal vom entsprechenden Wikipedia-Artikel ausgehend weiter informieren.

Benchmarking unter C# 👍 👎

Teilweise ist zu sehen, dass Entwickler unter C# Benchmarks mit Hilfe der DateTime-Struktur umsetzen. Diese ist für derartige Zwecke jedoch zu ungenau und es sollte stattdessen mit der etwas spezielleren Stopwatch-Klasse gearbeitet werden. Wie genau das nun aussehen kann, möchte ich gerne mit diesem Beitrag kurz aufzeigen:
Benchmark per Stopwatch durchführen
01020304050607080910
Stopwatch stopwatch = Stopwatch.StartNew();{      // Simulation einer längeren Ausführungszeit    for(int i = 0; i < 5; i++) {        Thread.Sleep(50);    }}stopwatch.Stop();
long elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
Darüber hinaus ist für besonders präzise Messungen insbesondere die ElapsedTicks-Eigenschaft interessant.

Selbstverständlich benötigt ein umfassendes Benchmarking einer Anwendung etwas mehr Komfort als eine schnelle Messung, es bietet sich daher beispielsweise auch eine entsprechende Suche im NuGet-Verzeichnis an.

GET- und POST-Anfragen mit C# 👍 👎

Für viele APIs – aber auch so manchen Test – ist es hilfreich, GET- und POST-Anfragen per HTTP senden zu können. Selbstverständlich ist das auch per C# sehr einfach möglich, wobei je nach gewünschtem Grad der Kontrollierbarkeit der Anfragen verschiedene Abstrahierungen zur Verfügung gestellt werden:

Die umfangreichsten Möglichkeiten (und geringen Abstraktiongrad) bietet HttpWebRequest:
GET per HttpWebRequest
01020304050607
HttpWebRequest webRequest = HttpWebRequest.CreateHttp("…/?key1=val1&key2=val2");
using(HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse()) { using(StreamReader streamReader = new StreamReader(webResponse.GetResponseStream())) { string response = streamReader.ReadToEnd(); }}
POST per HttpWebRequest
010203040506070809101112131415161718
HttpWebRequest webRequest = HttpWebRequest.CreateHttp("…");{    byte[] data = Encoding.UTF8.GetBytes("key1=val1&key2=val2");
webRequest.Method = "POST"; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.ContentLength = data.Length;
using(Stream stream = webRequest.GetRequestStream()) { stream.Write(data, 0, data.Length); }}
using(HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse()) { using(StreamReader streamReader = new StreamReader(webResponse.GetResponseStream())) { string response = streamReader.ReadToEnd(); }}
Deutlich kompakter lassen sich entsprechende Anfragen per WebClient formulieren:
GET per WebClient
010203
using(WebClient webClient = new WebClient()) {    string response = webClient.DownloadString("…/?key1=val1&key2=val2");}
POST per WebClient
010203040506
using(WebClient webClient = new WebClient()) {    string response = Encoding.UTF8.GetString(webClient.UploadValues("…", new NameValueCollection() {        {"key1", "val1"},        {"key2", "val2"}    }));}
Die aktuellste Möglichkeit mit Fokus auf asynchrone Vorgänge stellt HttpClient dar:
GET per HttpClient
010203
using(HttpClient client = new HttpClient()) {    string response = await client.GetStringAsync("…/?key1=val1&key2=val2");}
POST per HttpClient
01020304050607080910
using(HttpClient httpClient = new HttpClient()) {    HttpResponseMessage responseMessage = await httpClient.PostAsync("…",        new FormUrlEncodedContent(new Dictionary<string, string>() {            {"key1", "val1"},            {"key2", "val2"}        })    );
string response = await responseMessage.Content.ReadAsStringAsync();}
Grundsätzlich bestünde auch noch die Möglichkeit, auf TCP-Ebene unter Anwendung des HTTP-Protokolls vorzugehen – aber das wird man kaum benötigen. Abschließend sei noch erwähnt, dass diese Möglichkeiten nicht für fragliche Aktionen genutzt werden sollte – dazu gehört im Übrigen auch schlicht unnötiger Traffic.

Diagramme mit C# erstellen 👍 👎

Häufig möchte man Statistiken grafisch aufbereitet darstellen. Mit diesem Beitrag möchte ich gerne zeigen, wie sich mit Hilfe einiger unter System.Web.UI.DataVisualization.Charting befindlichen Klassen sehr einfach dynamisch Diagramme erzeugen lassen. Der korrespondierende Namespace für klassische Anwendungen lautet System.Windows.Forms.DataVisualization.Charting.

Für unser Beispiel verwenden wir eine einfache Windows-Anwendung und folgende Datensammlung:
Daten (einige BMW-Modelle)
010203040506
Dictionary<string, double> data = new Dictionary<string, double>() {    {"125i", 160},    {"335i", 225},    {"550i", 330},    {"760i", 400}};
Die Schlüssel bezeichnen das jeweilige Modell und der entsprechende Wert die Leistung (in kW). Daraus erzeugen wir nun ein einfaches Balkendiagramm und speichern das Ergebnis als dia.png:
Diagramm erstellen und speichern
01020304050607080910111213141516171819202122232425262728293031323334353637
using(Chart chart = new Chart() {    Height = 345,    Width = 543}) {      // Konfiguration    chart.Titles.Add("BMW-Leistungsvergleich");
chart.ChartAreas.Add(new ChartArea("statistic") { AxisX = new Axis() { MajorGrid = new Grid() { Enabled = false } }, AxisY = new Axis() { MajorGrid = new Grid() { LineColor = Color.LightGray, LineDashStyle = ChartDashStyle.Dot }, Title = "kW" } });
chart.Series.Add(new Series("data") { ChartType = SeriesChartType.Column });
// Daten foreach(KeyValuePair<string, double> entry in data) { chart.Series["data"].Points.Add(new DataPoint() { AxisLabel = entry.Key, YValues = new double[] { entry.Value } }); }
// Ausgabe chart.SaveImage("dia.png", ChartImageFormat.Png);}
Als grundlegende Konfiguration wurde in diesem Beispiel das Gitternetz im Hintergrund zur Orientierung auf horizontale Linien beschränkt und durch Farbänderung und Punktierung etwas subtiler gestaltet. Außerdem wurden ein paar Beschriftungen ergänzt und ansonsten die Standardkonfiguration belassen.

Wichtig zu verstehen ist das Konzept, welches zuerst eine ChartArea als Grundfläche und anschließend Series für die eigentlichen Daten erfordert. Es können jeweils mehrere davon in einem Diagramm untergebracht werden.

Selbstverständlich sind umfassende Optionen vorgesehen, die das Endergebnis praktisch völlig anders erscheinen lassen können – bis hin zum eigentlichen Diagrammtyp. Details zu den jeweiligen Klassen finden sich im MSDN.

XML-Dokument mit LINQ to XML verarbeiten 👍 👎

Obwohl andere Formate wie beispielsweise JSON immer mehr Verbreitung gewinnen, bleibt es natürlich nötig, XML verarbeiten zu können – mit diesem Beitrag möchte ich zeigen, wie XML-Dokumente mit LINQ to XML besonders einfach verarbeitet werden können. LINQ selbst werde ich separat noch ausführlich behandeln.

Als Ausgangslage für unser Beispiel soll folgendes XML-Dokument dienen:
Persons.xml
010203040506070809101112131415161718
<personList>    <person id="1">        <firstName>Anders</firstName>        <lastName>Hejlsberg</lastName>    </person>    <person id="2">        <firstName>Bjarne</firstName>        <lastName>Stroustrup</lastName>    </person>    <person id="3">        <firstName>James</firstName>        <lastName>Gosling</lastName>    </person>    <person id="4">        <firstName>Rasmus</firstName>        <lastName>Lerdorf</lastName>    </person></personList>
Wir möchten daraus eine (generische) Liste von Person-Objekten erzeugen. Die Klasse soll auch gleich die Methode zur XML-Verarbeitung erhalten, welche mit LINQ to XML nun besonders einfach umzusetzen ist:
Person.cs
010203040506070809101112131415161718192021222324252627
public class Person {    public int PersonID {        get;        set;    }
public string FirstName { get; set; }
public string LastName { get; set; }

public static List<Person> ParseXML(string xml) { return XElement.Parse(xml) .Descendants("person") .Select(p => new Person() { PersonID = Int32.Parse(p.Attribute("id").Value), FirstName = p.Element("firstName").Value, LastName = p.Element("lastName").Value }).ToList(); }}
Das Vorgehen dürfte eigentlich weitestgehend selbsterklärend sein. Obwohl selbstverständlich mehr Funktionalität zur Verfügung steht, dürften mit diesem kleinen Beispiel bereits die wichtigsten Fälle abgedeckt sein. Weitere Informationen finden sich ausgehend von XDocument im MSDN.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche