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

Grundsätzlich ist das Ansinnen möglichst viel Code wiederverwenden zu wollen absolut nachvollziehbar. Wenn wir einen Blick auf die - inzwischen wohl hinlänglich bekannten - Architekturdiagramme der beiden Betriebssysteme werfen, werden wir feststellen, dass es durchaus Bereiche gibt, die gleiche oder ähnliche Namen tragen und demzufolge doch Hoffnungen wecken, dass wir uns hier - für den Fall, dass wir eine App für WP8 als auch W8 entwickeln wollen - Arbeit sparen können. Und hier ist gleich ein ganz wichtiger Punkt festzuhalten: Wir müssen - da führt kein Weg vorbei - zwei separate Apps entwickeln. W8 und WP8 basieren zwar auf dem gleichen Betriebssystemkernel (was einige nette Nebeneffekte hat). Dennoch sind beides erst mal getrennte Betriebssysteme, die sich während der Entwicklung von Apps aber erstaunlich ähnlich anfühlen können. Unsere Aufgabe ist es also herauszufinden, wo diese Ähnlichkeiten liegen, um diese dann geschickt auszunutzen.

Für diesen Blogpost konzentriere ich mich auf den C#/XAML/Windows (Phone) Runtime Bereich in den Diagrammen. Für Cross-Plattform Entwicklung eignet sich grundsätzlich auch C++, in diesem Post spare ich dieses Thema allerdings aus. Man mag sich fragen, weshalb JS/HTML im Phone Diagramm gar nicht gelistet ist, wo es doch sogar Templates für WP8 App Entwicklung in Visual Studio für JS/HTML gibt. Der Hintergrund dafür ist, dass wir mit JS/HTML auf WP8 keine "nativen" Apps schreiben können, sondern diese immer in einem Webcontrol gehostet sein müssen. Bei W8 ist das anders, hier brauchen wir keinen Container, in dem das JS läuft, wir können direkt aus JS auf die (nativen) WinRT APIs zugreifen.

Verwirrend? Vielleicht! Einmal verstanden, weiß man aber, was geht und was nicht. Natürlich wäre es aus Entwicklersicht wünschenswert, wenn hier eine Vereinheitlichung der Programmiermodelle möglich wäre. Ich denke, dass wir mit C#/XAML wie ich es hier erläutern werde schon einen ordentlichen Weg einschlagen.

Generelle Strategie

Bevor wir in die Details gehen, sollten wir uns überlegen, was technisch nicht nur machbar, sondern auch sinnvoll ist. Wo lohnt es sich tatsächlich Elemente wiederzuverwenden? Die Antwort ist recht einfach: Überall da, wo wir nicht an die Benutzeroberfläche gebunden sind. Die Benutzeroberfläche für Windows Phone und Windows 8 sieht - bedingt durch die Kacheln - zunächst sehr ähnlich aus. Bestimmte Bedienelemente erkennt man als Anwender auch sehr leicht wieder, so dass sich ein WP Anwender auf W8 recht schnell heimisch fühlt und umgekehrt. Ein Beispiel hierfür sind die Live -Tiles. Dennoch gibt es auch Unterschiede, die wir respektieren müssen. So gibt es - sicherlich nicht zuletzt bedingt durch die Bildschirmgröße - unterschiedliche UI Controls, die für die Bedienung auf den jeweiligen Geräten optimiert sind. Um viele gruppierte Datensätze anzuzeigen gibt es auf dem Windows Phone das Pivot Control. Auf Windows 8 existiert dagegen das Grid View mit der Möglichkeit Semantic Zoom zu nutzen. Wir sollten als Entwickler trotz der Unterschiede dem Anwender die optimale Bedienung erlauben. Eine Windows Phone App ist nicht einfach nur eine geschrumpfte Windows Store App. Mit diesem Wissen folgt die Erkenntnis, dass wir, was die UI Gestaltung angeht, nicht viel Spielraum haben Code 1:1 wiederzuverwenden. Es gibt ein paar Hacks, die es erlauben eigene UI Controls für beide Plattformen zu erstellen oder XAML Dateien wiederzuverwenden. Meine Empfehlung ist an dieser Stelle etwas radikaler: Tut es nicht! Versucht besser die Bedienung Eurer App für das jeweilige Endgerät zu optimieren. Es gibt ja noch andere Ecken und Enden, an denen wir durch Wiederverwendung profitieren können. Im Zuge der Benutzerschnittselle stehen wir dennoch nicht mit leeren Händen da: Unser Wissen über XAML wird uns auf beiden Plattformen weiterhelfen.

Abkoppeln von nicht wiederverwend​baren Bereichen und MVVM

Mit diesem Wissen sollten wir versuchen nichtwiederverwendbare Teile von den wiederverwendbaren Teilen abzukoppeln. Beim UI gelingt das über Ansätze wie MVVM recht gut. Die View ist danach losgelöst von jeglicher Logik und kann - abhängig von der Zielplattform, neu implementiert und an das ViewModel gebunden werden. Auch andere Bereiche werden nicht oder nur schwer wiederverwendbar sein. So gibt es zwar ähnliche aber nicht identische Implementierungen des Process LifeCycle. Bei WP8 haben wir die Status Dormant und Tombstoned, bei W8 Suspended und Terminated. Die Konzepte sind ähnlich , es gibt aber Unterschiede. Statt hier auf Wiederverwendung von Code zu pochen, kümmern wir uns erst mal um das, was wir tatsächlich wiederverwenden können: Einen großen Teil unserer Anwendungslogik.

Portable Class Library

Am angenehmsten wäre für uns als Entwickler die Arbeit, wenn wir den Logik-Code einmalig schreiben, einmalig kompilieren würden und das daraus resultierende Binary einfach auf beiden Plattformen läuft. Good news: Das funktioniert, wenn auch mit Einschränkungen. In den Visual Studio 2012 Versionen ab Visual Studio Professional aufwärts gibt es eine Projektvorlage "Portable Class Library". Wenn wir diese auswählen, können wir Zielplattformen anwählen wie Windows Phone 8, Windows Store Apps und Xbox. Durch das Setzen von Häkchen können wir dafür sorgen, dass der Build-Output des Projekts binär kompatibel zu allen ausgewählten Zielplattformen ist. Das klingt fast zu schön um wahr zu sein, oder? Ok, es gibt eine Einschränkung, die wir verstehen müssen. Die Portable Class Library arbeitet nach dem Prinzip des kleinsten gemeinsamen Nenners im .Net Framework. Am besten ist das zu verstehen, wenn wir uns das .Net Framework als große Menge von Namespaces und Klassen vorstellen.

Wie wir vielleicht bereits wissen, ist auf dem Windows Phone nicht das komplette .Net Framework zur Verfügung, sondern lediglich eine Submenge, die auf die Anwendung auf dem Phone zugeschnitten ist (niemand braucht ASP.NET auf dem Phone, oder? Oder vielleicht doch? Ok - wir müssen mit dem Leben, was hier verfügbar ist.) Diese Untermenge nennt sich "Profil". Ebenso gibt es ein Profil, das auf die Windows Store Apps zugeschnitten ist - das ".NET Profile for Windows Store Apps". Beide Profile unterscheiden sich, sind Submengen des vollen .Net Profils, sind aber nicht deckungsgleich. Dennoch gibt es Schnittmengen. Ebenso verhält es sich mit den Profilen für die anderen auswählbaren Zielplattformen. Mit jedem Häckchen, das wir setzen reduzieren wir die Menge der zur Verfügung stehenden Namespaces auf die Schnittmenge aller Profile - und damit auf den kleinsten gemeinsamen Nenner. Die Entscheidung, welche Plattformen wir unterstützen wollen, sollten wir also sehr bewusst treffen, ein nachträgliches Aufnehmen einer weiteren Plattform wird - abhängig von der Aufgabe der Library - nur schwierig möglich sein.

Struktur in der Solution

Wenn wir uns jetzt eine Solution im Visual Studio aufbauen, sollten wir darauf achten, dass die Struktur noch verständlich bleibt. Ich lege daher immer mehrere Ordner an, deren Inhalt durch die Namensgebung schon klar sein sollte:

W8: Ein Ordner, in dem ich später das Windows Store App Projekt ablege und weitere Projekte und Libraries, die ausschließlich für diese App relevant sind.
WP8: Ein Ordner, in dem ich später das Windows Phone App Projekt ablege und alle zugehörigen Projekte und Libraries, die ausschließlich für WP relevant sind.
SharedLib: Hier lege ich meine Portable Library Projekte ab.
SharedCode: Hier lege ich später einzelne Code Files ab, die identisch in mehreren Projekten verwendet werden können - dazu später mehr.

Wenn wir jetzt anfangen Code zu schreiben, können wir versuchen möglichst viel in die Portable Library zu packen. Wenn ich hier alles reinpacken kann, habe ich tatsächlich sehr viel Arbeit gespart. Ich muss dann lediglich meine beiden Projekte (für WP8 und W8) anlegen und kann die Portable Libs referenzieren. Wenn ich dann noch jeweils ein eigenes UI für meine Anwendungen erstelle, bin ich fast am Ziel. Allerdings sind wir in dem, was in Portable Libraries geschehen kann stark begrenzt, da ja, wie bereits erwähnt, mit jeder Zielplattform die zur Verfügung stehenden Namespaces weniger werden.

Warnung: An dieser Stelle sei eine kleine Warnung ausgesprochen: Das Vorhaben des Cross Platform Development wird sich wohl oder übel auf den Aufbau Eurer Solution und auf die Architektur auswirken. Überlegt gut, ob Ihr Eure Anwendungsarchitektur auf dem Altar des Cross Platform Development opfern wollt - und wenn ja, wie weit es sinnvoll ist, dieses Spiel zu treiben. Abhängig davon wie groß Euer Projekt ist, wie viele Entwickler Ihr seid, wie viel Erfahrung diese haben etc. müsst das zuletzt Ihr selbst entscheiden. Seid vorsichtig, lasst Euch beraten und sorgt vor allem dafür, dass Ihr selbst und Euer Team den Aufbau am Ende noch verstehen!

Spätestens, wenn wir auf die Windows (Phone) Runtime zugreifen wollen, werden wir feststellen, dass die Portable Lib meckert - wir sind ab diesem Zeitpunkt nicht mehr binär kompatibel. Da jedoch die Windows Runtime und die Windows Phone Runtime wiederum nicht absolut unterschiedlich, sondern ähnlich sind, können wir hier eventuell Code wiederverwenden - der aber zielplattformabhängig neu kompiliert werden muss. Die Namespaces der Windows Runtime und der Windows Phone Runtime überlappen sich. Wir haben also einen Bereich von Funktionalität, deren API auf WP8 genauso implementiert ist wie auf W8. Solange wir uns in diesem Bereich aufhalten, schreiben wir genau die gleichen Zeilen Code für W8 wie für WP8 um Windows Runtime bzw. Windows Phone Runtime Aufrufe zu machen. Was uns das bringt und inwieweit wir mit dieser Aufgabe umgehen können - dazu folgt ein Post in den nächsten Tagen.

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.

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 zweimal 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.

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.

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 CodeKompatibilitä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 ref erenziert. 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:

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!):

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.

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 stattdessen 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 gestalten, 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.

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

Im ersten Teil dieser Serie haben wir einen Blick darauf geworfen, welche Möglichkeiten sich uns überhaupt bieten Code zwischen Windows 8 und Windows Phone 8 wiederzuverwenden. Wir haben die Portable Libraries kennengelernt und deren Einschränkungen erfahren. Im zweiten Teil haben wir uns unterschiedliche Methoden angesehen, wie wir durch geschickte Dateiverwaltung und ein paar Visual Studio Features Quellcode Artefakte zwischen mehreren Projekten teilen können. Natürlich können wir der Aufgabe plattformübergreifen Code zu entwickeln auch durch die KomponentenDesign Brille begegnen.

Vererbung

Wenn wir für zwei oder mehr Plattformen gleichzeitig entwickeln wollen (oder sollen), können wir diese Aufgaben ja auch wie folgt formulieren:

Wir entwickeln gleiche oder für mehrere Plattformen, wobei manche der Funktionen sich in der Implementierung unterscheiden (also plattformspezifisch sind), ein Teil der Funktionen aber gleich ist.

Bei Entwicklern von objektorientierten Sprachen klingelt’ s eventuell schon: Natürlich ist das ein Vererbungspräzedenzfall. Wir könnten also die gemeinsame Funktionalität einfach in Basisklassen auslagern und die plattformspezifischen Implementierungen von einzelnen Funktionen zusätzlich in abgeleiteten Klassen zur Verfügung stellen.
Ein Vorteil wäre die gemeinsame Basis, die nur einmal geschrieben und getestet werden muss. Ein Nachteil natürlich die Vererbungskette, die hier um eine Stufe wächst. Natürlich muss unser Klassendesign insgesamt stimmig sein - die Basisklasse können wir dann beispielsweise als Portable Class Library verpacken oder über "Add as Link" als Referenz zum jeweiligen Projekt hinzufügen.

Interfaces

Alternativ oder erweiternd dazu bietet sich das Arbeiten mit Interfaces an. Wie wir bereits erfahren haben, gibt es typischerweise einen Bereich des Codes, der plattformübergreifend gültig ist. Einzelne Bereiche sind aber plattformspezifisch zu implementieren. Wenn wir nun unsere wiederverwendbaren Klassen so designen, dass diese nicht mit konkreten Implementierungen arbeiten, sondern lediglich mit Interfaces, so können wir die Implementierungen dieser Interfaces in einer plattformspezifischen Klasse erledigen. Die konkrete plattformspezifische Implementierung wird dann bspw. im Konstruktor der plattformunabhängigen Komponente übergeben. Unterm Strich eine Form von Dependency Injection. Das Arbeiten mit Interfaces hat den Vorteil, dass wir uns ausgiebig mit dem Aufbau unserer Solution beschäftigen müssen und gegebenenfalls eine sehr modulare Lösung erzeugen. Der Nachteil liegt in einer - meinem Empfinden nach - etwas höheren Komplexität.

Delegates

Bisweilen funktioniert der Mechanismus, den ich über die Interfaces erreichen kann auch mit Delegates. In diesem Falle ruft mein plattformunabhängiger Code kein Interface auf, sondern eben einen Delegate. Das ist auf den ersten Blick ein bisschen leichtgewichtiger, als die Definition eines Interfaces. Persönlich empfinde ich das Arbeiten mit Interfaces aber als sauberer, da man die Interfaces bereits "von außen" erkennt, beispielsweise im Architekturtool des Visual Studio (siehe auch oben), während man die Delgaten erst im Code entdeckt. Statt ein Inteface zu implementieren könnten wir so einfach eine Methode übergeben, die unter bestimmten Umständen aufgerufen wird.

Up-/Down-Casting

Um meinen Blog Post hier auch mit praktischer Erfahrung zu belegen, habe ich ein paar kleinere Projekte für Windows 8 und Windows Phone 8 entwickelt, mit dem Vorsatz diese so zu strukturieren, dass ich möglichst wenig plattformspezifischen Code schreiben muss. Ich habe dabei alle hier genannten Mechanismen genutzt, will aber nicht verhehlen, dass ich auch noch ein bisschen mehr verwenden musste.
Als etwas unsauber empfand ich dabei das Up-/Downcasting auf den Typen "Object". In meiner Portable Class Library habe ich Referenzen auf Objekte gespeichert, die - abhängig davon auf welcher Plattform die Lib zum Einsatz kam - unterschiedlichen Typs waren. Ich musste hier mit einer Referenz arbeiten - der einfachste Weg schien mir das Hinterlegen als "Object" zu sein. In der jeweiligen Plattform musste ich dann bei Verwendung dieser Referenz entsprechend casten. Das konnte ich natürlich problemlos tun, da ich ja auf der jeweiligen Plattform weiß, welchen Typ ich erwarte. Richtig sauber fühlt sich das leider nicht an, aber es schien mir eine pragmatische Lösung zu sein. Natürlich empfiehlt es sich hier ein wenig über Exception Handling nachzudenken...

Fazit

Damit bin ich vorerst am Ende meiner Serie über Cross Platform Development zwischen Windows Phone und Windows 8. Die hier genannten Vorgehensweisen könnt Ihr aber ebenso gut auch bei Entwicklung von Serverkomponenten, Desktopsoftware oder sonstiger Windows-Entwicklung heranziehen. Natürlich ist Tauglichkeit und Nutzen immer abhängig vom konkreten Szenario, aber es schadet sicher nicht, sie alle in einem Blogpost gelistet zu finden.

Was ist jetzt mein Fazit? Mein persönliches Fazit ist deutlich besser, als ich erwartet hatte, bevor ich mich mit der Materie befasst habe. Plattformübergreifende Entwicklung mit C# für Windows 8 und Windows Phone 8 ist möglich - wenn man sich mit den Rahmenparametern beschäftigt. Das umfasst unterschiedliche .NET Profile, Unterschiede in der Windows Runtime und im UI sowie im Laufzeitverhalten von Applikationen. Die Logik der Anwendung jedoch ist weitgehend unbeeinflusst - und hier kann man mit den genannten Vorgehensweisen punkten.

Ist also alles super? Fairerweise muss ich auch hier etwas bremsen: Die Plattformunabhängigkeit bekommt man nicht umsonst. Es wird in jedem Falle etwas Zeit kosten unsere Lösung plattformunabhängig zu gestalten. Ein paar der hier aufgezeigten Vorgehensweisen lohnen aber ohnehin einer näheren Betrachtung, da sie ein paar nette Nebeneffekte Mitbringen - Modularität zum Beispiel. Insofern lohnt sich das Investment vielleicht so oder so.

Mehr zum Thema