Schlagwort: Theorie

Byte-Angaben mit C# formatieren 👍 👎

Mit gewisser Regelmäßigkeit kommt man als Softwareentwickler in die Verlegenheit, Dateigrößen statt in der Form 10110 Byte in der "üblichen" Schreibweise mit größeren Einheiten (in diesem Fall 9.87 KiB) darzustellen.

Zwar gäbe es hier wieder einmal die Möglichkeit per DllImport und beispielsweise StrFormatByteSizeW zu arbeiten, jedoch möchten wir in diesem Fall die Implementierung selbst vornehmen, um die Basis (Stichwort: Binärpräfixe) konsistent bestimmen zu können. Ich stelle dazu zwei mögliche Implementierungen vor:
Hilfsklasse mit Methode zur Formatierung
0102030405060708091011121314151617
public static class Helper {    private static readonly IReadOnlyList<string> unitList = new string[] {        "Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"    };

public static string FormatBytes(double bytes) { int index = 0;
while(bytes >= 1024 && index < Helper.unitList.Count) { bytes /= 1024; index++; }
return $"{Math.Round(bytes, 2).ToString("N2")} {Helper.unitList[index]}"; }}
Alternativ lässt sich das Problem jedoch auch ohne Schleife – und daher mathematisch etwas eleganter – durch ein ähnliches Vorgehen wie bei der Ermittlung der Anzahl der Ziffern einer Zahl mittels Logarithmen lösen:
Alternativer Lösungsansatz
010203040506
public static string FormatBytes(double bytes) {    int index = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));    bytes /= Math.Pow(1024, index);
return $"{Math.Round(bytes, 2).ToString("N2")} {Helper.unitList[index]}";}
Die Verwendung gestaltet sich nun in jedem Fall recht einfach:
Formatierung verwenden
010203
string formattedBytes = Helper.FormatBytes(1024);         // 1,00 KiBformattedBytes = Helper.FormatBytes(1024 * 1024);         // 1,00 MiBformattedBytes = Helper.FormatBytes(1024 * 1024 * 1024);  // 1,00 GiB
Abschließend möchte ich auch noch kurz eine beispielhafte Implementierung zur Verwendung im Rahmen einer benutzerdefinierten Formatierung per String.Format skizzieren:
Implementierung der benutzerdefinierten Formatierung
010203040506070809
public class FileSizeFormatProvider : ICustomFormatter, IFormatProvider {    public string Format(string format, object arg, IFormatProvider formatProvider) {        return Helper.FormatBytes(Convert.ToDouble(arg));    }
public object GetFormat(Type formatType) { return this; }}
Bitte beachtet jedoch, dass diese Implementierung noch nicht vollständig ist. Weiterführende Informationen zur korrekten Implementierung finden sich im MSDN per ICustomFormatter und IFormatProvider. Verwendung:
Verwendung der benutzerdefinierten Formatierung verwenden
01
formattedBytes = String.Format(new FileSizeFormatProvider(), "{0}", (1024 * 1024));  // 1,00 MiB
Ich werde eine vollständige Implementierung vermutlich in einem späteren Beitrag nachreichen.

Wissen(schaft)spodcasts 👍 👎

In früheren Beiträgen hatte ich bereits ein paar Podcast-Empfehlungen ausgesprochen und auf mein Feedreader-Projekt hingewiesen, welches ebenfalls einige Podcasts aus verschiedenen Themenbereichen bereithält.

Mit diesem Beitrag möchte ich darüber hinaus gerne auf Wissenschaftspodcasts hinweisen. Diese Seite wurde von einigen Podcastern aus dem Bereich der Wissenschafts- und Wissensvermittlung ins Leben gerufen. Zu den Gründern gehören u. a. auch Nicolas Wöhrl von methodisch inkorrekt und Markus Völter von omega tau – beides Podcasts, die ich schon seit sehr langer Zeit verfolge und auch hier bereits beworben habe.

Die von den genannten und weiteren Personen eröffnete Seite bietet nun eine Sammlung verschiedenster Podcasts aus den Bereichen Wissen und Wissenschaft. Das Themenspektrum ist breit abgedeckt und reicht von Archäologie, Geschichte und Technik über Astronomie, Forschung im Allgemeinen und Speziellen bis hin zu Mathematik und Naturwissenschaften. Ich gehe also davon aus, dass für (fast) alle meiner – sicherlich hauptsächlich technisch interessierten – Besucher etwas dabei sein dürfte. Ihr könnt darüber hinaus auch neue Vorschläge einreichen.

Da mir die Vermittlung von Wissen und das Gespräch über wissenschaftliche Erkenntnisse persönlich äußerst wichtige Angelegenheiten sind, würde ich mich sehr freuen, wenn ihr etwas Passendes findet und die Seite(n) weiterempfehlt. Der Vollständigkeit wegen möchte ich abschließend noch kurz darauf hinweisen, dass ich mit den genannten Seiten in keiner weiteren Verbindung außer als Zuhörer einiger Podcasts stehe.

Schaltjahr ermitteln 👍 👎

Viele Beispiele ermitteln ein Schaltjahr über eine Prüfung darauf, ob das fragliche Jahr durch vier teilbar ist. Diese einfache Prüfung, die bereits im julianischen Kalender Anwendung findet, ist jedoch nur eine von drei Regeln des gregorianischen Kalenders. Für eine vollständige Prüfung müssen folgende drei Bedingungen überprüft werden:
  • Ist das Jahr durch 4 teilbar, ist es potentiell ein Schaltjahr. (2016 ist ein Schaltjahr)
  • Ist das Jahr durch 100 teilbar, ist es grundsätzlich kein Schaltjahr. (2100 ist kein Schaltjahr)
  • Ist das Jahr durch 400 teilbar, ist es generell ein Schaltjahr. (2000 ist ein Schaltjahr)
Dies lässt sich in C# nun beispielsweise wie folgt als statische Methode umsetzen:
Schaltjahr-Prüfung implementieren und verwenden
01020304050607
public static bool IsLeapYear(int year) {    return (((year % 2 == 0) && (year % 100 != 0)) || (year % 400 == 0));}
bool isLeapYear = IsLeapYear(2016); // trueisLeapYear = IsLeapYear(2100); // falseisLeapYear = IsLeapYear(2000); // true
Besonders spannend ist diese Methode jedoch nicht, das .NET-Framework bietet mit DateTime.IsLeapYear(…) nämlich bereits eine entsprechende Implementierung an. Es gilt jedoch den Hinweis der Dokumentation zu beachten, dass die Prüfung dabei immer im Rahmen des gregorianischen Kalenders erfolgt, was für manche – beispielsweise historische – Anwendungen unpässlich sein kann. Das Framework bietet im Namensraum System.Globalization jedoch weitere Kalender-Implementierungen an, welche jeweils eine entsprechende Umsetzung der IsLeapYear(…)-Methode zur Verfügung stellen.

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.

123

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche