Archiv der Kategorie: .NET

Aufgepretzelt – von statisch zu generiert – Teil 4

Pretzel

Pretzel

Mit Hilfe von Pretzel haben wir im dritten Teil die ersten redundanten Informationen ausgelagert. Im vierten und letzten Teil sehen wir, wie das Menü ausgelagert werden kann und was man bei Websites tun kann, die verschiedene Layouts verwenden.

Menü
Die meisten Websites bestehen aus mehr als einer Seite und nutzen zur Navigation zwischen den Seiten ein Menü. Um dem Benutzer anzuzeigen, wo er sich gerade befindet, wird der aktuelle Menüpunkt hervorgehoben. Dadurch sieht das Menü auf jeder Seite ein kleines bisschen anders aus, enthält aber immer die gleichen Menüpunkte, welche auch ausgelagert werden sollen.

Um das Menü mittels Pretzel auszulagern kann man den folgenden Aufbau im Layout benutzen:

<ul class="menu">
  <li {% if page.url == "/index.html" %} class="active"{% endif %}><a href="index.html">Home</a></li>
  <li {% if page.url == "/seite1.html" %} class="active"{% endif %}><a href="seite1.html">Seite 1</a></li>
  <li {% if page.url == "/seite2.html" %} class="active"{% endif %}><a href="seite2.html">Seite 2</a></li>
  <!-- etc. -->
</ul>

Dadurch wird bei der Seite, welche gerade bearbeitet wird (sprich, bei welcher die URL übereinstimmt) die css-class active gesetzt. Die Quelle für diese Variante ist Jekyll Tips. Dort wird auch auf den Nachteil dieser einfachen Lösung hingewiesen: Neue oder umbenannte Seiten müssen im Layout nachgeführt werden, sonst erscheinen sie im Menü nicht. Falls nur wenige Änderungen anfallen, kann dies aber verschmerzt werden. Die dort aufgeführte automatische Variante scheint mit Pretzel (noch) nicht zu funktionieren.

Zwei Layouts
Einige Websites nutzen neben dem Layout für die normalen Seiten ein zweites Layout, z.B. für den Blog oder eine Fotogalerie. Dazu kann ein weiteres Datei im Ordner _layouts erstellt werden. In den Dateien, welche das zweite Layout verwenden sollen, kann dann auf das neue Layout verwiesen werden.

Dadurch sind aber Redundanzen schon wieder vorprogrammiert. Dies kann aber durch includes verhindert werden. Die in beiden Layouts vorhandenen Fragmente können in Dateien ausgelagert werden, welche im Ordner _includes abgelegt werden müssen. Im Layout kann dann das Fragment mit {% include footer.html %} eingefügt werden.

Abschluss
Weitere individuelle Elemente sollten mit den oben gezeigten Grundlagen umgesetzt werden können. Ansonsten ist die Dokumentation von Jekyll eine gute Anlaufstelle, da die Beschreibung von Pretzel nur die Pretzel-spezifischen Informationen und die Unterschiede zu Jekyll enthält.

Sobald die Migration abgeschlossen ist können die geplanten Änderungen (welche ja der Auslöser für die ganze Arbeit waren) vorgenommen werden. Es empfiehlt sich dabei, den Mechanismus für die Sicherheitskopie beizubehalten.

Fazit
Mit diesen einfachen Grundlagen sollten die meisten Webseiten umgebaut werden können, so dass Änderungen einfacher vonstatten gehen. Gewisse Funktionen sind aber noch nicht komplett von Jekyll übernommen worden.

Aufgepretzelt – von statisch zu generiert – Teil 3

Pretzel

Pretzel

Nachdem wir im zweiten Teil die Struktur generiert und die Website ein erstes Mal generiert haben wollen wir nun vom Einsatz von Pretzel profitieren. Wir lagern redundante Informationen Schritt für Schritt zentral aus.

Jeder Durchgang besteht aus den drei Schritten identifizieren, modifizieren und kontrollieren.

Identifizieren
Als erstes müssen wir den HTML-Code der Seiten analysieren, um geeignete Teile zu finden, die zentral ausgelagert werden können. Dazu kommt das Compare-Tool zum Einsatz. Wir vergleichen dabei zwei verschiedene Seiten und suchen uns Blöcke, die identisch sind und auch thematisch sinnvoll ausgelagert werden können. Ein erster Kandidat ist dabei oftmals der Footer der Seiten. Wir können dies auch noch weiter überprüfen, indem wir weitere Seiten miteinander vergleichen.

Modifizieren
Wenn wir uns nun entschieden haben, können wir den auszulagernden Teil aus einer Datei kopieren, eine neue Datei layout.html im Ordner _layouts erstellen und den auszulagernden Inhalt dort einfügen. Dann müssen wir noch angeben, wo der individuelle Inhalt (der aus den verschiedenen Dateien kommt) eingefügt wird. Dies erfolgt durch den Platzhalter {{content}}. Entsprechend sollte dann die Datei layout.html folgende Struktur aufweisen, wenn der Footer ausgelagert wurde:

{{content}}
<!-- auszulagernder Footer -->

Damit nun die Seiten auch das Layout verwenden muss dies in jeder Datei angegeben werden (sogenannte Meta-Informationen). Dies geschieht durch folgenden Aufbau:

---
layout: layout
---
<!-- bisherige Seite, ohne Footer -->

Der Footer-Teil muss dabei auch bei jeder Datei entfernt werden, da er sonst doppelt erscheint.

Kontrollieren
Wenn wir alle Dateien entsprechend angepasst haben können wir Pretzel erneut mit dem Aufruf pretzel bake starten. Nachdem Pretzel seine Arbeit beendet hat können wir das Resultat im Ordner _site erneut mit der Ausgangs-Website vergleichen. Es sollten weiterhin keine Unterschiede vorhanden sein. Falls dies der Fall ist, sollte wiederum eine Sicherheitskopie angelegt werden.

Same same but different
Was aber ist zu tun, wenn auszulagernde Teile nur fast gleich sind, sich aber in kleinen, aber wichtigen Details unterscheiden? Dies tritt zum Beispiel oft beim Header der Seiten auf, die zwar dieselbe Struktur aber einen individuellen Titel haben.

Hierzu können in den Meta-Informationen Variablen definiert werden, auf welche im Template zurückgegriffen werden kann.

Eine Seite würde dann z.B. wie folgt aussehen:

---
layout: layout
title: Startseite
---
<!-- bisherige Seite, ohne Header und Footer -->

Das dazu passende Layout hätte folgenden Aufbau:

<!-- erster Teil des Headers -->
 <title>Meine tolle Website - {{ page.title }}</title>
<!-- zweiter Teil des Headers -->
{{content}}
<!-- auszulagernder Footer -->

Wiederum kontrollieren wir nach den Änderungen das Resultat und erstellen eine Sicherheitskopie.

Fehlerbehandlung
Bisher haben wir nur den Schönwetterfall betrachtet: Die Änderungen bewirken das gewünschte Resultat und wir können den nächsten Durchgang starten. Doch was können wir tun, wenn das Resultat nicht den Erwartungen entspricht?

Der einfachste Fall, aber manchmal trotzdem schwer zu erkennen, ist, wenn man beim Zugriff auf die Variablen einen Schreibfehler macht. So definiert man z.B. die Variable title (englische Schreibweise), versucht dann aber auf titel (deutsche Schreibweise) zuzugreifen. Eine nicht definierte Variable enthält dann einfach einen leeren Text, entsprechend wird auch nichts ausgegeben – leider auch kein Fehler.

Wenn man Fallunterscheidungen (mit if) oder Schleifen (mit for) verwendet kann es sinnvoll sein, den Wert einer Variablen einfach einmal auszugeben um so zu überprüfen, ob sie den erwarteten Inhalt hat. Dies kann man auch in einen HTML-Kommentar verpacken, um das Layout nicht zu stören:

<!-- DEBUG: URL der Seite: "{{ page.url }}" -->

Ausblick
Wir haben nun die ersten redundanten Informationen ausgelagert. Wie das Menü ausgelagert werden kann und was man bei Websites tun kann, die verschiedene Layouts verwenden, sehen wir im vierten und letzten Teil.

Aufgepretzelt – von statisch zu generiert – Teil 2

Pretzel

Pretzel

Nach den Grundlagen im ersten Teil erstellen wir nun die Struktur und lassen die Seiten aus den bestehenden statischen Seiten erzeugen. Dabei lagern wir aber noch keine Elemente in Templates aus.

Struktur erzeugen:

Pretzel wird über die Kommandozeile bedient, deshalb muss als erstes die Eingabeaufforderung oder die PowerShell geöffnet werden.

Mittels pretzel create kann die Struktur erzeugt werden. Diese wird im aktuellen Ordner erzeugt. Weitere Kommandos können über den Aufruf pretzel angesehen werden.

Die erzeugte Struktur gibt bereits eine Beispiel-Website mit einem Blog aus, wenn man sie generieren lässt. Da wir aber unsere bestehende Website migrieren wollen löschen wir die Beispiel-Dateien. Dies betrifft die Dateien about.md, atom.xml, index.html,rss.xml und sitemap.xml im Hauptverzeichnis sowie die Inhalte aller Ordner.

Dateien und Ordner mit bzw. ohne ‚_‘ am Anfang:

In der generierten Struktur fällt auf, dass es zwei unterschiedliche Arten von Dateien bzw. Ordnern gibt: solche mit und solche ohne ‚_‘ am Anfang.

Die Dateien und Ordner mit einem ‚_‘ am Anfang des Namens sind Hilfs-Dateien und -Ordner. Diese enthalten Informationen, die für die Generierung der Webseite zu Rate gezogen werden. So enthält die Datei _config.yml Konfigurationsdaten, welche die Generierung der gesamten Webseite beeinflussen. Der Ordner _layouts enthält Informationen, wie eine einzelne Seite aufgebaut werden soll.

Alle anderen Dateien und Ordner enthalten den Inhalt der Website und werden je nach Dateityp durch Pretzel verarbeitet (z.B. durch Informationen aus den Hilfs-Dateien angereichert) und das Resultat schlussendlich im Ordner _site abgelegt.

Website übernehmen:

Um die Website zu übernehmen kopieren wir die zu migrierende statische Website inklusive aller Unterordner und deren Inhalte in das Hauptverzeichnis. Mit dem Aufruf pretzel bake sollte dann das Unterverzeichnis _site entstehen, welches die generierte Website als Inhalt hat.

Mit Hilfe des Compare Tools können wir nun den Inhalt des Ordners _site mit der zu migrierenden Website verglichen werden. Wenn alles geklappt hat sollten keine Unterschiede festzustellen sein, da Pretzel nichts geändert sondern nur die Dateien übernommen hat.

Dies wäre jetzt der passende Zeitpunkt, um das erste Mal eine Sicherungskopie zu erstellen.

Ausblick:

Im dritten Teil werden wir die redundanten Informationen Schritt für Schritt zentral auslagern, um vom Einsatz von Pretzel profitieren zu können.

Aufgepretzelt – von statisch zu generiert – Teil 1

Pretzel

Pretzel

Es beginnt mit einer einzelnen, einfachen Web-Seite. Etwas HTML, etwas CSS, vielleicht auch noch ein bisschen Javascript. Dann kommt eine Zweite hinzu und mit der Dritten hält dann auch das Menü Einzug in die Website. Da dieses Menü in allen statischen Seiten vorhanden ist müssen dann beim Hinzufügen der vierten Seite alle anderen Seiten angepasst werden, damit alle das gleiche Menü haben.

Dann kommt der Gedanke: „Das muss doch einfacher gehen!“

Der Einsatz eines CMS (z.B. WordPress, Typo3, Silverstripe etc.) ist für ein paar wenige Seiten aber doch zu viel des Guten und es müssten dann auch regelmässig (Sicherheits-)Updates eingespielt werden.

Hier kommen die Website-Generatoren wie Jekyll, Hugo oder Pretzel zum Zug. Mit einem Website-Generator werden die Seiten lokal aus verschiedenen Dateien zusammengebaut. Die gesamte Website kann dann in der Form von statischen HTML-Dateien auf den Webserver hochgeladen werden.

Da in diesem Fall die Website schon besteht und vorerst keine Änderungen angebracht werden sollen beschreibe ich hier die Migration von statischen HTML-Seiten zu einer generierten Website. Dazu setze ich Pretzel ein, das Vorgehen kann aber für andere Website-Generatoren übernommen werden.

Voraussetzung:
Die HTML-Seiten sollten alle einen identischen, sauberen Aufbau haben. D.h. die einzelnen Seiten sollten sich nur in den relevanten Bereichen unterscheiden und es sollten z.B. keine fehlenden Menüeinträge auf einzelnen Seiten vorhanden sein.

Benötigte Software:

  • Website-Generator, in diesem Fall Pretzel in der Version V0.4.0.0.
  • Compare-Tool, um Dateien und Ordner zu vergleichen. Ich setze hierzu die Gratisversion von Code Compare ein. Als Alternative könnte z.B. Beyond Compare (ist kostenpflichtig) eingesetzt werden.
  • Die Dateien können zwar auch mit Notepad editiert werden, es empfiehlt sich aber ein komfortablerer Editor wie Notepad++ oder Visual Studio Code einzusetzen.

Ausserdem empfiehlt es sich, für die einzelnen Schritte des Umbaus Sicherungskopien anzulegen. Dies kann durch kopieren oder zippen des Arbeitsordners oder durch den Einsatz einer Versionskontrolle wie Git oder Subversion (SVN) erfolgen. Wenn man zur Versionskontrolle z.B. Github einsetzt hat man zudem noch eine Sicherungskopie ausser Haus.

Grundidee der Migration:
Am Ende der Migration möchten wir wieder dieselben Web-Seiten haben, die wir jetzt schon als statische Seiten haben. Dadurch sind wir sicher, dass der migrierte Webauftritt dem bisherigen Webauftritt entspricht. Sobald dies erfüllt ist können wir dann die gewünschten Änderungen vornehmen.

Dazu gehen wir Schritt für Schritt vor und ändern immer nur einen Aspekt, generieren dann die Website neu und vergleichen das Resultat mit den statischen Seiten. Wenn nun Unterschiede vorhanden sind analysieren wir, warum diese Unterschiede entstehen und passen unsere Änderung an. Sobald keine Unterschiede mehr ausgewiesen werden können wir eine Sicherungskopie anlegen und uns dem nächsten Aspekt widmen. Dies machen wir solange, bis alle nötigen Umbauarbeiten durchgeführt sind.

Ausblick:
Im nächsten Teil erstellen wir die Struktur und lassen die Seiten aus den bestehenden statischen Seiten erzeugen, ohne irgendwelche Elemente schon in Templates auszulagern.

Slippery When Wet #8: Parsing IP addresses in C#

I proudly present to you the eighth in a infinite number of posts of “Slippery When Wet.” In these posts I show you a little bastard I stumbled on.

When I was trying to read some IP addresses from a CSV file I parsed them with the method IPAddress.Parse() from the System.Net namespace. Everything worked fine up to the eighth ip address. There the Parse method threw a System.FormatException: The format of the IP address was not valid. The IP address that caused the problem looked like ‚127.000.000.008‘. In my opinion this looked like a valid number, although it could be written a little bit shorter.

But Microsoft sees the world a little bit different. According to the answer by Mario Cossi on the problem description I found on social.msdn.microsoft.com this behaviour is completly valid, as all the numbers with leading zeros should be considered as octal numbers and therefore 008 is not valid, as 8 in not in the range of octal numbers. Mario writes that this is defined by standards, but does not refere to the standard where this is defined.

I did some more research but could not find the standard that describes this behaviour. What I did find were some older RFCs (like RFC 790) where IP addresses (or ranges) were written in the format with the leading zeros.

According to a wikipedia article and a therein referenced draft there is no defined standard for the textual representation of a IPv4 address.

In my opinion, the design decision of Microsoft is not the best. At least there should be a possibility to set the expected behaviour in case of a leading zero octet. Especially as the IP address 010.010.010.010 could be interpreted as 10.10.10.10 or 8.8.8.8 and you would not get an error as it would be in octal as well as in decimal a valid number.

As Microsoft did not give this possibility, according to your data you have to read, you should possibily use your own parsing method instead of the built-in one.

Wie erstellt man am besten Objektkopien?

Image courtesy of "Stuart Miles" / FreeDigitalPhotos.net

Image courtesy of „Stuart Miles“ / FreeDigitalPhotos.net

In einem Projekt benötigen wir nicht veränderliche Objekt-Instanzen, die aber als Kopien in eine zweite Struktur eingefügt werden sollen, wobei an einigen Properties teilweise kleine Änderungen vorgenommen werden müssen.

Die Klasse sieht etwa wie folgt aus:

public class MyClass
{
  public IContainer Container { get; private set; }
  public int ImutableValue1 { get; private set; }
  public string ImutableValue2 { get; private set; }
  public int MutableValue3 { get; private set; }
  public string MutableValue4 { get; private set; }

  public MyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    Container = container;
    ImutableValue1 = imutableValue1;
    ImutableValue2 = imutableValue2;
    MutableValue3 = mutableValue3;
    MutableValue4 = mutableValue4;
    if(container != null)
    {
      container.Register(this);
    }
  }
}

Um in die neue Struktur hinzugefügt zu werden muss der container gesetzt werden. Die anderen Werte können gesetzt werden, ansonsten wird der Default-Wert übernommen.

Für das Erstellen der Kopie gab es am Anfang folgenden Code, der im Client angesiedelt war:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = new MyClass(newContainer, myClass.ImutableValue1, myClass.ImutableValue2, myClass.MutableValue3, myClass.MutableValue4);
  }

  // Do some other things
}

Dieser Code beinhaltet folgende Probleme:

  • Der Client hat bzw. benötigt zu viel Wissen über die Klasse.
  • Wenn ein Property ändert oder ein neues hinzukommt müssen alle Stellen, an welchen dieser Kopier-Code vorhanden ist, angepasst werden.
  • Die Gefahr ist gross, dass bei einem neuen Property einige Stellen vergessen werden und dann in der Kopie immer der Default-Wert (und nicht der Instanz-Wert) eingetragen wird. Je nach Tests und Test-Abdeckung kann dies erst sehr spät, sprich erst im regulären Betrieb, auffallen.

Auf der Suche nach einer Lösung haben wir verschiedene Ansätze diskutiert.

Copy Constructor

Der Copy Constructor ist besonders aus der C++-Welt bekannt. Dort ist immer mindestens ein (impliziter) Copy Constructor vorhanden.

Für das oben gezeigte Beispiel würde der Copy Constructor etwa wie folgend aussehen:

public class MyClass
{
  // ...

  public MyClass(MyClass source, IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    Container = newContainer;
    ImutableValue1 = source.ImutableValue1;
    ImutableValue2 = source.ImutableValue2;
    MutableValue3 = mutableValue3 != null ? mutableValue3 : source.MutableValue3;
    MutableValue4 = mutableValue4 != null ? mutableValue4 : source.MutableValue4;
    if(Container != null)
    {
      Container.Register(this);
    }
  }
}

Der Client-Code würde dann so aussehen:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = new MyClass(myClass, newContainer);
  }

  // Do some other things
}

Clone()-Funktion

Im C#-Umfeld ist die Clone-Funktion verbreiteter als der Kopierkonstruktor, besonders durch das Interface IClonable des .NET-Frameworks. Da wir aber keine 1:1-Kopie der Objekt-Instanz erstellen wollen können wir das Interface hier nicht einsetzen.

Die Implementation unserer Clone-Funktion könnte etwa so aussehen:

public class MyClass
{
  // ...

  public MyClass Clone(IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    var newClass = new MyClass(
                                newContainer,
                                ImutableValue1,
                                ImutableValue2,
                                mutableValue3 != null ? mutableValue3 : MutableValue3,
                                mutableValue4 != null ? mutableValue4 : MutableValue4
                              );
    return newClass;
  }
}

Mit der Clone()-Funktion würde sich dann der Client-Code wie folgt präsentieren:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = myClass.Clone(newContainer);
  }

  // Do some other things
}

Factory

Die dritte Variante besteht aus einer Factory, die sich um die Erzeugung der MyClass-Instanzen kümmert. Der Konstruktor der Klasse MyClass sollte dann auf internal gesetzt werden um die Erzeugung über die Factory zu erzwingen.

public class MyClassFactory
{
  public static MyClass CreateMyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    var myClass = new MyClass(container, imutableValue1, imutableValue2, mutableValue3,  mutableValue4);
    return myClass;
  }
 
  public static MyClass CopyMyClass(MyClass source, IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    var newClass = new MyClass(
                                newContainer,
                                source.ImutableValue1,
                                source.ImutableValue2,
                                mutableValue3 != null ? mutableValue3 : source.MutableValue3,
                                mutableValue4 != null ? mutableValue4 : source.MutableValue4
                              );
    return newClass;
  }
}

public class MyClass
{
  // ...

  internal MyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    // ...
  }
}

Für den Einsatz der Factory wäre folgender Client-Code nötig:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = MyClassFactory.CopyMyClass(myClass, newContainer);
  }

  // Do some other things
}

Vergleich

Der Kopierkonstruktor und die Clone-Funktion haben beide den Nachteil, dass der Benutzer der Klasse die Varianten schnell übersehen kann. Dadurch würde trotzdem Code wie im anfänglichen Client-Code-Beispiel geschrieben. Durch das Verschieben der Erzeugungs-Funktionen in eine Factory sind die Varianten der Erzeugung schnell ersichtlich und der Benutzer kann so die passende Variante wählen.

Aus diesem Grund haben wir dann auch die Variante mit der Factory gewählt.

Reflektion KW 41

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Ich habe mir zu Ziel gesetzt, jede Woche hier im Blog kurz zu reflektieren. Diese Woche starte ich und versuche, dies zumindest bis Ende Jahr durchzuziehen.

Pair Programming
Diese Woche habe ich an zwei Tagen mit zwei verschiedenen Team-Mitgliedern im Pair Programming an zwei Tasks gearbeitet. Es waren zwei intensive Tage, die aber zu guten Ergebnissen in kurzer Zeit geführt haben.

Halbwertszeit des Wissens
Obwohl ich selbst über das Problem mit den Namespaces in XML gebloggt hatte bin ich gut zwei Jahre später wieder in dieselbe Falle getappt. Aber wenigstens ist mir schnell eingefallen, dass ich dieses Problem schon einmal hatte.

Binäre Werte in C#

Image courtesy of "Stuart Miles" / FreeDigitalPhotos.net

Image courtesy of „Stuart Miles“ / FreeDigitalPhotos.net

Da ich mich wieder einmal in die Tiefen eines Protokolls herab wagen musste war ich auch mit Bitmasken konfrontiert. Da merkte ich, dass ich nicht mehr wusste, wie binäre Werte in C# anzugeben sind. Also kurz Dr. Google gefragt und die Antwort bei StackOverflow gefunden: Geht nicht!

Mit der neuen Compiler-Platform „Roslyn“ (C# 6) waren „binary literals“ geplant (Das Feature war für C# im Status „planned“, für VB.NET schon „done“), sind nun aber nicht mehr für den nächsten Release vorgesehen.

Ein paar Vorschläge, wie man sich behelfen kann sind bei StackOverflow auch noch zu finden:

Ein anwendbarer Weg, wenn nicht zu viele Werte nötig sind, ist der Vorschlag von Markus Johnsson:

Man kann die Werte als Konstanten definieren, die mit b… beginnen:

const int b001 = 1;
const int b010 = 2;
const int b011 = 3;
// etc ...
Debug.Assert((b001 | b010) == b011);

Eine andere Variante (von Marc Gravell) ist, mit Strings zu arbeiten und diese zu Parsen:

int i = Convert.ToInt32("01101101", 2);

Sahuagin vertritt in seiner Antwort die Meinung, dass man gefälligst mit hexadezimalen Werten rechnen lernen soll…

Aber der Vorschlag von Dmitry Tashkinov dürfte mehr Ärger verursachen als helfen, wenn er angewendet wird:

long bitMask = 1011001;
// And later
int bit5 = BitField.GetBit(bitMask, 5);
// Or
bool flag5 = BitField.GetFlag(bitMask, 5);`

// Helper class
public static class BitField
{
    public static int GetBit(int bitField, int index)
    {
        return (bitField / (int)Math.Pow(10, index)) % 10;
    }

    public static bool GetFlag(int bitField, int index)
    {
        return GetBit(bitField, index) == 1;
    }
}

Entsprechend auch der Kommentar dazu: Using int thousand to mean 1-0-0-0 is just asking for trouble.

Welcher Constructor wird aufgerufen?

Image courtesy of "artur84" / FreeDigitalPhotos.net

Image courtesy of „artur84“ / FreeDigitalPhotos.net

Seit der Version 4.0 sind in C# Methoden mit Parametern erlaubt, die einen Default-Wert besitzen. Dies kann auch auf Contructor-Methoden angewendet werden, also z.B.

1
2
3
4
5
6
7
8
9
10
11
12
public class A
{
  public A()
  {
    // ...
  }
 
  public A(string x = "default")
  {
    // ...
  }
}

Die Frage ist nun: Welcher Constructor wird verwendet, wenn ich eine Instanz mit A a = new A(); erzeuge?

Versuch 1

Im oben beschriebenen Fall kann dies schnell beantwortet werden, indem man es einfach ausprobiert:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace ConstructorQuiz1
{
  class Program
  {
    static void Main(string[] args)
    {
      A a1 = new A();
      A a2 = new A("fix");
    }
  }
 
  public class A
  {
    public A()
    {
      System.Console.WriteLine("A()");
    }
 
    public A(string x = "default")
    {
      System.Console.WriteLine(x);
    }
  }
}

Und hier das Resultat:

A()
fix

Man sieht also, dass der „am besten passende“ Constructor verwendet wird und der Default-Wert keine Verwendung findet. (wenn der Standard-Constructor A() nicht existieren würde, würde natürlich der Default-Wert verwendet).

Versuch 2

Wenn man aber mit den Access Modifiers herumspielt ist das ganze nicht mehr so eindeutig. Setzen wir einmal den Access Modifier des Standard-Constructors auf internal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace ConstructorQuiz2
{
  class Program
  {
    static void Main(string[] args)
    {
      A a1 = new A();
      A a2 = new A("fix");
    }
  }
 
  public class A
  {
    internal A()
    {
      System.Console.WriteLine("A()");
    }
 
    public A(string x = "default")
    {
      System.Console.WriteLine(x);
    }
  }
}

Das Ergebnis ist immer noch das selbe, hier hat der geänderte Access Modifier also noch keine Wirkung gezeigt.

Versuch 3

Versuchen wir jetzt aber mal von Aussen zuzugreifen:

1
2
3
4
5
6
7
8
9
10
11
namespace ConstructorQuiz3
{
  class Program
  {
    static void Main(string[] args)
    {
      ConstructorQuiz2.A a1 = new ConstructorQuiz2.A();
      ConstructorQuiz2.A a2 = new ConstructorQuiz2.A("fix");
    }
  }
}

Dieser Code muss mit einer Referenz auf den compilierten Code aus dem zweiten Beispiel enthalten. Dies erfolgt beim Command Line Tool csc mit dem Parameter /r=<filename>.

Das Resultat sieht nun anders aus:

default
fix

Es wird jetzt also der Constructor mit dem Default-Wert aufgerufen, da der Standard-Constructor von der Dritt-Assembly nicht aufgerufen werden darf.

Fazit

Wann welcher Constructor verwendet wird ist nicht nur von den Parametern sondern auch von den Access Modifiern und dem Kontext abhängig.

NSIS Plugin „CLR“ verwenden

NSIS & .NET

NSIS & .NET

Mit C# und dem .NET Framework von Microsoft kann man oftmals einfacher eine Funktionalität implementieren als mit C++, besonders da man sich dank dem Garbage Collector nicht (oder weniger) um das Speicherhandling kümmern muss.

Wenn man dies auch in einem NSIS-Installer nutzen möchte (evtl. hat man den Code bereits vorhanden und möchte ihn nicht noch einmal implementieren) kann man das NSIS-Plugin CLR verwenden.

Dabei sind aber ein paar Punkte zu beachten, damit der Installer auch wirklich funktioniert.

  1. Das .NET Framework muss installiert sein.
    Ohne das .NET Framework kann auch die gewünschte Funktion nicht ausgeführt werden. Je nach Systemvoraussetzungen kann aber davon ausgegangen werden, dass dies schon erfüllt ist. Eine Beschreibung, wie das .NET Framework mit NSIS installiert wird kann im Artikel „Simple Way To Install .NET Framework“ nachgelesen werden.
  2. Das .NET Assembly muss mit .NET 2.0 lauffähig sein.
    Das Plugin CLR wurde mit dem .NET Framework 2.0 erstellt. Dementsprechend können keine Assemblies gestartet werden, die eine neuere .NET Version voraussetzen (z.B. durch den Einsatz von LINQ). In Visual Studio kann das Ziel-Framework in den Project-Optionen angegeben werden. Wenn auf der Kommandozeile mittels csc.exe kompiliert wird muss csc.exe aus dem .NET Framework 2.0 Ordner verwendet werden.
    Um diese Einschränkung zu umgehen kann das Plugin selber mit der passenden .NET Version übersetzt werden. Der Sourcecode ist im .ZIP File enthalten.
  3. Das .NET Assembly muss im Installer enthalten sein.
    Das Plugin fügt das .NET Assembly nicht automatisch dem Installer hinzu. Deshalb müssen das .NET Assembly und alle weiteren nötigen Assemblies mit dem File-Kommando dem Installer hinzugefügt werden.

Noch ein paar Tipps und weitere Quellen:

  • Ausgaben von der .NET Assembly in die NSIS-Konsole.
    Wenn die .NET Assembly eine Arbeit verrichtet, die eine längere Zeit in Anspruch nimmt oder man dem Benutzer anzeigen möchte, was die DLL ausgeführt hat, kann man Anhand des Artikels „DetailPrint From Inside .NET DLL“ die Ausgaben vornehmen.
  • Zusätzliche Hinweise zum Plugin „CLR“.
    Im Forumsthread „Calling managed .NET DLL from NSIS – this works :-)“ ist die Entstehungsgeschichte des Plugins beschrieben und es werden weitere Hinweise zur Verwendung gegeben (Seiten 2 und 3 auch beachten).

Habt ihr dieses Plugin auch schon eingesetzt und wie sind eure Erfahrungen. Fehlen hier noch Fallstricke, die erwähnt werden sollten?