Vous avez une bonne question. Il y a probablement des compromis avec votre solution. La réponse ultime dépend vraiment de ce que vous entendez par dépendant de la plate-forme. Par exemple, si vous démarrez un processus pour démarrer des applications externes et que vous passez simplement d'une application à une autre, vous pouvez probablement gérer cela sans trop de complications. Si vous parlez de P / Invoke avec des bibliothèques natives, il y a un peu plus à faire. Cependant, si vous établissez une liaison avec des bibliothèques qui n'existent que sur une seule plate-forme, vous devrez probablement utiliser plusieurs assemblys.
Applications externes
Vous n'aurez probablement pas besoin d'utiliser des #if
instructions dans cette situation. Il vous suffit de configurer certaines interfaces et d'avoir une implémentation par plate-forme. Utilisez une usine pour détecter la plate-forme et fournir la bonne instance.
Dans certains cas, il s'agit simplement d'un binaire compilé pour une plate-forme spécifique, mais le nom de l'exécutable et tous les paramètres sont définis de la même manière. Dans ce cas, il s'agit de résoudre le bon exécutable. Pour une application de conversion audio en vrac qui pouvait fonctionner sur Windows et Linux, j'ai eu un initialiseur statique pour résoudre le nom binaire.
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);
}
}
Rien d'extraordinaire ici. Juste de bons cours à l'ancienne.
P / Invoke
P / Invoke est un peu plus délicat. L'essentiel est que vous devez vous assurer que la bonne version de la bibliothèque native est chargée. Sur Windows, vous feriez P / Invoke SetDllDirectory()
. Différentes plates-formes peuvent ne pas avoir besoin de cette étape. C'est donc là que les choses peuvent devenir désordonnées. Vous devrez peut-être utiliser des #if
instructions pour contrôler quel appel est utilisé pour contrôler la résolution de votre chemin d'accès à la bibliothèque, en particulier si vous l'incluez dans votre package de distribution.
Lien vers des bibliothèques dépendantes de la plateforme complètement différentes
L'approche multi-ciblage de la vieille école peut être utile ici. Cependant, cela vient avec beaucoup de laideur. À l'époque où certains projets tentaient d'avoir la même DLL cible Silverlight, WPF et potentiellement UAP, vous deviez compiler l'application plusieurs fois avec différentes balises de compilation. Le défi avec chacune des plates-formes ci-dessus est que, bien qu'elles partagent les mêmes concepts, les plates-formes sont suffisamment différentes pour que vous deviez contourner ces différences. C'est là que nous entrons dans l'enfer de #if
.
Cette approche nécessite également l'édition manuelle du .csproj
fichier pour gérer les références dépendantes de la plateforme. Étant donné que votre .csproj
fichier est un fichier MSBuild, il est tout à fait possible de le faire de manière connue et prévisible.
#si l'enfer
Vous pouvez activer et désactiver des sections de code à l'aide d' #if
instructions afin de gérer efficacement les différences mineures entre les applications. En surface, cela semble être une bonne idée. Je l'ai même utilisé comme moyen d'activer et de désactiver la visualisation de la boîte englobante pour déboguer le code de dessin.
Le problème numéro 1 #if
est qu'aucun des codes désactivés n'est évalué par l'analyseur. Vous pouvez avoir des erreurs de syntaxe latentes, ou pire, des erreurs de logique qui vous attendent pour recompiler la bibliothèque. Cela devient encore plus problématique avec le refactoring de code. Quelque chose d'aussi simple que de renommer une méthode ou de changer l'ordre des paramètres serait normalement géré correctement, mais parce que l'analyseur n'évalue jamais quoi que ce soit désactivé par l' #if
instruction, vous avez soudainement cassé du code que vous ne verrez pas avant de recompiler.
Tout mon code de débogage qui a été écrit de cette manière a dû être réécrit après qu'une série de refactorisations l'ait interrompu. Pendant la réécriture, j'ai utilisé une classe de configuration globale pour activer et désactiver ces fonctionnalités. Cela l'a rendu refactorisé, mais une telle solution n'aide pas lorsque l'API est complètement différente.
Ma méthode préférée
Ma méthode préférée, basée sur de nombreuses leçons douloureuses apprises, et même basée sur le propre exemple de Microsoft, consiste à utiliser plusieurs assemblys.
Un seul assemblage NetStandard définira toutes les interfaces et contiendra tout le code commun. Les implémentations dépendantes de la plate-forme seraient dans un assemblage séparé qui ajouterait des fonctionnalités lorsqu'elles sont incluses.
Cette approche est illustrée par la nouvelle API de configuration et l'architecture d'identité actuelle. Comme vous avez besoin d'intégrations plus spécifiques, vous ajoutez simplement ces nouveaux assemblages. Ces assemblages fournissent également des fonctions d'extension pour s'intégrer dans votre configuration. Si vous utilisez une approche d'injection de dépendances, ces méthodes d'extension permettent à la bibliothèque d'enregistrer ses services.
C'est à peu près le seul moyen que je connaisse pour éviter l' #if
enfer et satisfaire un environnement sensiblement différent.