Du hast eine gute Frage. Es gibt wahrscheinlich einige Kompromisse mit Ihrer Lösung. Die endgültige Antwort hängt wirklich davon ab, was Sie unter plattformabhängig verstehen. Wenn Sie beispielsweise einen Prozess zum Starten externer Anwendungen starten und einfach zwischen einer Anwendung und einer anderen wechseln, können Sie dies wahrscheinlich ohne allzu große Komplikationen tun. Wenn Sie mit nativen Bibliotheken über P / Invoke sprechen, müssen Sie noch ein wenig mehr tun. Wenn Sie jedoch mit Bibliotheken verknüpfen, die nur auf einer Plattform vorhanden sind, müssen Sie wahrscheinlich mehrere Assemblys verwenden.
Externe Apps
#if
In dieser Situation müssen Sie wahrscheinlich keine Anweisungen verwenden. Richten Sie einfach einige Schnittstellen ein und verwenden Sie eine Implementierung pro Plattform. Verwenden Sie eine Fabrik, um die Plattform zu erkennen und die richtige Instanz bereitzustellen.
In einigen Fällen handelt es sich nur um eine Binärdatei, die für eine bestimmte Plattform kompiliert wurde, aber der Name der ausführbaren Datei und alle Parameter sind gleich definiert. In diesem Fall geht es darum, die richtige ausführbare Datei zu finden. Für eine Massen-Audio-Konverter-App, die unter Windows und Linux ausgeführt werden konnte, ließ ich den Binärnamen mit einem statischen Initialisierer auflösen.
public class AudioProcessor
{
private static readonly string AppName = "lame";
private static readonly string FullAppPath;
static AudioProcessor()
{
var platform = DetectPlatform();
var architecture = Detect64or32Bits();
FullAppPath = Path.combine(platform, architecture, AppName);
}
}
Hier ist nichts Besonderes. Nur gute, altmodische Klassen.
P / Invoke
P / Invoke ist etwas kniffliger. Unter dem Strich müssen Sie sicherstellen, dass die richtige Version der nativen Bibliothek geladen ist. Auf Windows würden Sie P / Invoke SetDllDirectory()
. Verschiedene Plattformen benötigen diesen Schritt möglicherweise nicht. Hier kann es also zu Problemen kommen. Möglicherweise müssen Sie #if
Anweisungen verwenden, um zu steuern, welcher Aufruf zum Auflösen Ihres Bibliothekspfads verwendet wird - insbesondere, wenn Sie ihn in Ihr Verteilungspaket aufnehmen.
Verknüpfung mit völlig anderen plattformabhängigen Bibliotheken
Hier kann der Multi-Targeting-Ansatz der alten Schule hilfreich sein. Es kommt jedoch mit viel Hässlichkeit. In den Tagen, in denen einige Projekte versuchten, dasselbe DLL-Ziel Silverlight, WPF und möglicherweise UAP zu verwenden, mussten Sie die Anwendung mehrmals mit verschiedenen Kompilierungstags kompilieren. Die Herausforderung bei jeder der oben genannten Plattformen besteht darin, dass die Plattformen zwar dasselbe Konzept aufweisen, sich jedoch ausreichend unterscheiden, sodass Sie diese Unterschiede umgehen müssen. Hier kommen wir in die Hölle von #if
.
Bei diesem Ansatz muss die .csproj
Datei auch manuell bearbeitet werden, um plattformabhängige Verweise zu verarbeiten. Da es sich bei Ihrer .csproj
Datei um eine MSBuild-Datei handelt, ist dies auf bekannte und vorhersehbare Weise möglich.
#wenn die Hölle
Sie können Codeabschnitte mithilfe von #if
Anweisungen aktivieren und deaktivieren, um die geringfügigen Unterschiede zwischen den Anwendungen wirksam zu bewältigen. An der Oberfläche klingt es nach einer guten Idee. Ich habe es sogar als Mittel zum Ein- und Ausschalten der Bounding-Box-Visualisierung zum Debuggen von Zeichnungscode verwendet.
Das Problem Nummer 1 #if
besteht darin, dass keiner der deaktivierten Codes vom Parser ausgewertet wird. Möglicherweise treten latente Syntaxfehler oder, noch schlimmer, logische Fehler auf, die darauf warten, dass Sie die Bibliothek erneut kompilieren. Dies wird beim Refactoring von Code noch problematischer. Etwas so Einfaches wie das Umbenennen einer Methode oder das Ändern der Reihenfolge von Parametern wird normalerweise in Ordnung gehandhabt. Da der Parser jedoch niemals etwas auswertet, das durch die #if
Anweisung deaktiviert wurde, ist plötzlich Code kaputt, den Sie erst beim erneuten Kompilieren sehen.
Mein gesamter Debug-Code, der auf diese Weise geschrieben wurde, musste neu geschrieben werden, nachdem eine Reihe von Refactorings ihn gebrochen hatte. Während des Umschreibens habe ich eine globale Konfigurationsklasse verwendet, um diese Funktionen ein- und auszuschalten. Das hat es zum Refactor-Tool gemacht, aber eine solche Lösung hilft nicht, wenn die API völlig anders ist.
Meine bevorzugte Methode
Meine bevorzugte Methode, basierend auf vielen schmerzhaften Lektionen und sogar basierend auf dem Beispiel von Microsoft, besteht darin, mehrere Assemblys zu verwenden.
Eine zentrale NetStandard-Assembly definiert alle Schnittstellen und enthält den gesamten allgemeinen Code. Die plattformabhängigen Implementierungen befinden sich in einer separaten Assembly, die bei Einbeziehung Funktionen hinzufügt.
Dieser Ansatz wird durch die neue Konfigurations-API und die aktuelle Identitätsarchitektur veranschaulicht. Wenn Sie spezifischere Integrationen benötigen, fügen Sie einfach diese neuen Assemblys hinzu. Diese Assemblys bieten auch Erweiterungsfunktionen, mit denen Sie sich in Ihre Konfiguration integrieren können. Wenn Sie einen Ansatz zur Abhängigkeitsinjektion verwenden, können die Bibliotheken mit diesen Erweiterungsmethoden ihre Dienste registrieren.
Nur so kann ich der #if
Hölle aus dem Weg gehen und ein wesentlich anderes Umfeld befriedigen.