Windows 8 + Windows Phone 8 Cross Platform App Development mit C#/XAML (Teil 2)

Im letzten Teil haben wir die Luxusvariante kennengelernt, mit der wir für unterschiedliche Microsoft Plattformen nahezu ohne Mehraufwand programmieren können, die Portable Class Library. Wir haben außerdem herausgefunden, dass die Portable Class Library Einschränkungen mit sich bringt, die wir kennen und verstehen sollten und mit denen wir leben müssen. Des Weiteren kommen Entwickler, die die Express Versionen von Visual Studio nutzen leider nicht in den Genuss der Portable Class Libraries, weil diese erst aber der Professional Version zur Verfügung stehen. Grund genug also, sich anzusehen, welche Alternativen wir haben. Die Portable Class Libraries ermöglichen uns Binär-Kompatibilität. Das ist sehr angenehm für Entwickler, da der Code nur einmal kompiliert werden muss und dennoch auf allen Zielplattformen läuft, aber im Prinzip wäre uns ja auch schon geholfen, wenn wir Code wiederverwenden könnten.

Code Kompatibilität 

Dadurch dass die Windows Phone Runtime und die Windows Runtime sich zu großen Teilen überlappen, schreiben wir die exakt gleichen Zeilen Code, wenn wir auf die Menge der APIs in diese Schnittmenge zugreifen – egal für welche der beiden Plattformen. Wir sind also “code-kompatibel” und müssen lediglich für beide Plattformen neu kompilieren. Was auf den ersten Blick erstaunlich positiv klingt, kann sich bei näherem Hinsehen leider dennoch als ziemlich umständlich erweisen: Würde das etwa bedeuten, dass wir den Quellcode aus einem Windows Phone Projekt zu gewissen Teilen per Copy & Paste in ein Windows 8 Projekt einfügen? Wäre das nicht unglaublich umständlich? Und vor allem: Wer soll das bitte pflegen? Jeder Entwickler, der schon mal Applikationen jenseits von “Hello World”- Beispielen programmiert hat, wird hier (zurecht) das Schlimmste befürchten. Ohne Unterstützung durch die Entwicklungsumgebung ist Code-Kompatibilität so attraktiv wie Nudeln ohne Soße und für professionelle Entwicklung nur sehr eingeschränkt zu gebrauchen. Man könnte natürlich als Argument anführen, dass zumindest das Wissen über die gemeinsamen APIs wiederverwendet werden kann und mit Sicherheit die Entwicklung der jeweils zweiten Plattform schneller voran gehen wird als bei der ersten. Das trifft in jedem Falle zu, der Code ist ja schon bekannt. Aber zum Glück müssen wir so gar nicht argumentieren – denn wir haben ja im Visual Studio eine Toolunterstützung, wie wir sie uns wünschen. Erstaunlicherweise stelle ich immer wieder fest, dass sie nicht so bekannt ist, wie sie sein sollte.

Referenzen auf Quellcode Dateien - Add as Link

Wenn wir als Entwickler den exakt gleichen Quellcode aus einer Datei in mehreren Projekten gleichzeitig nutzen wollen, dann ist das bereits erwähnte Copy & Paste aus genannten Gründen wohl nicht die beste Wahl. Visual Studio gibt uns die Möglichkeit über den “Add” Dialog nicht nur komplett neu Dateien zu Projekten hinzuzufügen, sondern auch bestehende (Add exisiting Item). Im darauf folgenden Dialog können wir über einen Dateibrowser eine Datei anwählen.

image

Der Haken an der Sache ist, dass beim Hinzufügen einer bestehenden Datei aus einem anderen Projekt eine Kopie der ausgewählten Datei angelegt wird. Würden wir also eine Datei im Windows Phone 8 Projekt anlegen und anschließend aus einem Windows 8 Projekt diese Datei hinzufügen, hätten wir die Datei zwei mal physikalisch auf der Festplatte liegen (einmal im jeweiligen Projektordner) und hätten lediglich eine vereinfachte Variante des rein manuellen Copy & Paste bewirkt. Was wir jedoch wollen, ist eine Referenz auf eine existierende Datei. Und siehe da: Im “Add existing Item” Dialog gibt es neben dem “Add” Button ein kleines Pfeilchen, über das wir den “Add as Link” Befehl auswählen können. Sobald wir das tun wird die Datei nicht physikalisch kopiert, sondern als Referenz eingefügt.

image

Für uns als Entwickler heißt das: Wenn wir einmal Code in der Datei ändern, sind automatisch beide Projekte betroffen. Wenn wir einmal neue Funktionalität einbauen, so steht diese in beiden Projekten zur Verfügung, wenn wir einmal einen Bug fixen, dann erfahren beide Projekte diesen Fix. Im Umkehrschluss müssen wir auch beide Projekte testen, sobald wir die Datei modifiziert haben. Dateien, die nicht als physikalische Datei hinzugefügt wurden, sondern lediglich als Referenz, erkennen wir am kleinen blauen Pfeilchen im Logo im Solution Explorer. So sehen wir auf einen Blick, ob wir die Datei physikalisch oder als Referenz vorliegen haben. Eine solche referenzierte Datei kann – logischerweise – auch nur einmal im Visual Studio geöffnet sein. Würde man versuchen die Datei neu aus einem anderen Projekt der gleichen Visual Studio Instanz zu öffnen, so meldet uns Visual Studio, dass das nicht möglich ist, da sie schon im Rahmen eines anderen Projektes geöffnet ist.

image

Wann immer wir die Windows Runtime im Bereich der Schnittmenge zwischen Windows Phone Runtime und Windows Runtime ansprechen, können wir hier von gleichem Code und diesem Feature profitieren und den Code für mehr als nur ein Projekt nutzen. Ich persönlich lege diese gemeinsam genutzten Dateien in einem separaten Solution Folder ab, den ich bereits im letzten Posting angekündigt habe: Der SharedCode Folder. Damit ist klar, dass die Datei – egal aus welchem Projekt ich diese letztlich referenziere – nicht einem Projekt exklusiv gehört.

Partial Classes

Die beiden bisherigen Strategien Shared Code über Referenzen und Portable Class Libraries eignen sich sehr gut, um ViewModels im MVVM-Aufbau abzubilden. Allerdings werden wir bei der Verwendung auch hier früher oder später an die Grenzen des Trivialen stoßen. Vermutlich werden wir in unseren Projekten auch plattformspezifische Bereiche der Runtime nutzen. Beispielsweise Windows 8 Runtime APIs, die es auf dem Phone nicht gibt und umgekehrt. Dadurch, dass in der Regel eine Datei in C# eine Klasse darstellt und wir durch die Add-As-Link-Strategie immer vollständige Dateien referenzieren, schränkt uns die Anwendung dieser Strategie de facto darauf ein innerhalb einer Klasse, die wir teilen möchten, ausschließlich in der Schnittmenge der Windows Runtime zu operieren. Wenn wir hier flexibler sein möchten, bietet sich aber eine sehr einfache Lösung an: Das Verwenden von Partial Classes.

Das Feature der Partial Classes ist schon seit einigen .NET Versionen vertreten und letztlich gibt es uns die Möglichkeit den Inhalt einer Klasse über mehrere Dateien in Einzelteile (Parts) zu zerlegen. Durch das Keyword “partial” markieren wir eine Klasse als Partial Class. Der Compiler erkennt dann, dass er vor dem Kompilieren nochmal nach links und rechts blicken muss, um nach weiteren Parts zu suchen. Wenn er welche findet, dann bindet er diese mit in den Kompiliervorgang mit ein. Wenn nicht, dann eben nicht.

Wenn wir nun also eine Klasse geschrieben haben, in der beispielsweise 9 von 10 der Methoden Code-Kompatibilität aufweisen und lediglich eine aus dem Ruder läuft, da hier eine plattformspezifische API angesprochen wird, bietet es sich an diese eine Methode in einen separaten Part zu schieben und somit vom Rest des Codes zu trennen. An dieser Stelle sollten wir uns auf eine Namenskonvention einigen, da sonst schnell der Punkt erreicht ist, an dem niemand mehr versteht, was wohin und wozu gehört. Die Namenskonvention übernehmen wir von den XAML Dateien: Wenn wir uns diese genau ansehen, werden wir feststellen, dass es zusätzlich zu den Dateiname.xaml Dateien auch noch eine Dateiname.xaml.cs Datei finden. Der naheliegende Vorschlag ist also den Dateinamen beizubehalten und über einen zusätzlichen Namenszusatz zu markieren: Dateiname.W8.cs für Windows 8 spezifischen Code, Dateiname.WP8.cs für Windows Phone 8 spezifischen Code.

Um die Struktur einfach zu halten, wäre mein Vorschlag, dass der gemeinsame Teil der Klasse wieder im SharedCode Folder abgelegt wird. Von dort wird er in den jeweiligen Projekten referenziert. Die plattformspezifischen Klassenteile werden im jeweiligen Projekt abgelegt – sind also nur dort zu finden, wo sie wirklich von Bedeutung sind.

Das Ganze könnte im Code dann wie folgt aussehen:

image

Und im Visual Studio Solution Explorer so (in diesem Fall habe ich die beiden Klassenteile Sensors.cs und Sensors.W8.cs nicht direkt ins Windows 8 Projekt gehängt, sondern eine weitere Library angelegt, die alle Shared Anteile beinhaltet und diese wiederum aus meinem eigentlichen Windows Store App Projekt referenziert. Nicht verwirren lassen!):

image

Unterm Strich bleibt stehen: Durch Partial Classes erhöhen wir die Granularität der wiederverwendbaren Häppchen. Plattformspezifische Besonderheiten können wir durch weitere Teilklassen hinzufügen. Unsere wiederverwendbaren Teile entsprechen also nicht mehr zwingend einer ganzen Klasse entsprechen, sondern wir können auch nur mit Teilbereichen von Klassen arbeiten.

Auch an dieser Stelle scheint mir eine Warnung sinnvoll: Wie Ihr seht, erhöhen wir hier Schritt für Schritt die Wiederverwendbarkeit einzelner Assets innerhalb unserer Projektmappe. Das ist ja auch Ziel dieses Postings. Bitte beachtet, dass im gleichen Maße die Komplexität steigt, der Ihr als Entwicklerteam gegenübersteht.  Was im Beispiel mit “Hello World”-Niveau ganz locker geht, wird beim Entwickeln im 100-Mann Team vielleicht nicht ganz so reibungslos verlaufen. Vielleicht hat sich auch jemand den oberen Abschnitt schon 2 mal durchgelesen um ihn zu verstehen? Falls ja: Geht davon aus, dass das Eure Kollegen auch tun müssen. Dennoch sind Partial Classes eine feine Sache – überlegt Euch einfach, wo Ihr im Verhältnis Aufwandsersparnis vs. Komplexität steht und ob diese Strategien für Euren Anwendungsfall eine valide Möglichkeit darstellt. Erklärt den neuen Mitgliedern in Eurem Team, wie der Quellcode aufgebaut ist. Partial Classes nutzt in dieser Verwendung sicher nicht jeder.

Bedingte Kompilierung

Nachdem wir uns jetzt ausgehend von physikalischen Dateien über Klassen auf Klassenteile (und damit nicht zuletzt einer bestimmten Menge von Methoden)heruntergehangelt haben, können wir noch einen Schritt weiter gehen: Wie wär’s denn, wenn wir einzelne Codezeilen wiederverwenden könnten? Auch das kriegen wir im Visual Studio über Bedingte Kompilierung hin. Bedingte Kompilierung funktioniert über kleine Code Schnipsel, mit denen wir den Compiler dazu überreden können bestimmte Codeabschnitte wahlweise einzubinden oder zu ignorieren. In dem Szenario, in dem wir uns befinden bedeutet das, dass wir eine Datei über “Add-As-Link” in unsere beiden Zielprojekte einbinden. Über Anweisungen im Code können wir dann bestimmte Aufrufe, die nur für eine der beiden Plattformen gilt ein und ausblenden.

Diese Aufrufe sind nichts anderes als eine if-Bedingung mit vorangestelltem #, das die Zielplattform abfragt. Wenn die gleiche Datei einmal in ein W8 und einmal in ein WP8 Projekt eingebunden wird, wird abhängig davon welches Projekt gerade angewählt ist der jeweils ungültige Quellcode ausgegraut und der andere normal dargestellt. Im Prinzip könnten wir über diesen Mechanismus das Verfahren mit den Partial Classes ersetzen.

image

 

Allerdings lautet meine Empfehlungen hier anders: Ich bin zwar nicht der Meinung, dass Bedingte Kompilierung generell böse ist, aber sie sorgt dafür, dass der Quellcode in kürzester Zeit sehr sehr (diese beiden “sehr” sind durchaus als Steigerung zu sehen) undurchschaubar wird. Als Entwickler bekommt man durch die Farbgebung der IDE zwar eine gewisse Unterstützung um herauszufinden, welcher Code gerade relevant ist. Dennoch sehen wir als Entwickler ständig Codeabschnitte ohne Bedeutung für unser aktuelles Projekt und arbeiten mit mehr Zeilen Code als wir benötigen. Für alle Codeabschnitte, die über sehr wenige Zeilen hinausgehen, würde ich das Verwenden von Compiler Anweisungen nur unter Vorbehalt empfehlen und statt dessen zu Partial Classes tendieren, falls sich das umsetzen lässt.

Für using-Statements am Dateianfang hingegen eignen sie sich sehr gut – falls also die gleiche Funktion auf W8 und WP8 in unterschiedlichen Namespaces zu finden ist, so wäre das ein sehr gutes Einsatzgebiet der Compiler Anweisungen. An dieser Stelle spare ich mir die Warnung. Ich denke, ich bin bereits in der Erläuterung genug auf die Nachteile der Compiler-Anweisungen eingegangen.

Dieser Post zeigt, dass wir unter Verwendung von .NET ein paar schöne Möglichkeiten haben unseren Quellcodeaufbau so zu gestallten, dass er wiederverwendbar wird. Nichts davon ist wirklich neu für Windows 8 oder Windows Phone 8 Apps – wir müssen lediglich den Aufbau unserer Lösung auf den geplanten Anwendungsfall hin optimieren. So kommen wir mit ein bisschen Basis-Handwerkszeug unserem eigentlichen Ziel noch ein bisschen näher. Natürlich lassen sich die hier beschriebenen Strategien hervorragend mit der Verwendung von Portable Class Libraries kombinieren. Und dann könnten wir uns ja neben dem physikalischen Aufbau unserer Solution auch noch mit dem logischen Aufbau beschäftigen … aber dafür gibt’s einen neuen Post.

 

Update 21.5.2013:

Teil 1

Teil 2

Teil 3