Hai una buona domanda Probabilmente ci sono alcuni compromessi con la tua soluzione. La risposta definitiva dipende davvero da cosa intendi per piattaforma dipendente. Ad esempio, se stai avviando un processo per avviare applicazioni esterne e stai semplicemente passando da un'applicazione all'altra, probabilmente puoi gestirlo senza troppe complicazioni. Se stai parlando di P / Invoke con librerie native, c'è ancora qualcosa da fare. Tuttavia, se si esegue il collegamento con librerie che esistono solo su una piattaforma, probabilmente sarà necessario utilizzare più assembly.
App esterne
Probabilmente non avrai bisogno di usare le #if
dichiarazioni in questa situazione. Basta impostare alcune interfacce e avere un'implementazione per piattaforma. Utilizzare una fabbrica per rilevare la piattaforma e fornire l'istanza giusta.
In alcuni casi è solo un binario compilato per una piattaforma specifica, ma il nome dell'eseguibile e tutti i parametri sono definiti uguali. In tal caso si tratta di risolvere l'eseguibile giusto. Per un'app di conversione audio di massa che poteva essere eseguita su Windows e Linux, un inizializzatore statico risolveva il nome binario.
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);
}
}
Niente di speciale qui. Solo buone vecchie lezioni di moda.
P / Invoke
P / Invoke è un po 'più complicato. La linea di fondo è che è necessario assicurarsi che sia caricata la versione corretta della libreria nativa. Su Windows dovresti P / Invocare SetDllDirectory()
. Piattaforme diverse potrebbero non aver bisogno di questo passaggio. Quindi è qui che le cose possono diventare confuse. Potrebbe essere necessario utilizzare le #if
istruzioni per controllare quale chiamata viene utilizzata per controllare la risoluzione del percorso della libreria, in particolare se la si include nel pacchetto di distribuzione.
Collegamento a librerie dipendenti dalla piattaforma completamente diverse
L'approccio multi-targeting della vecchia scuola può essere utile qui. Tuttavia viene fornito con molta bruttezza. Nei giorni in cui alcuni progetti hanno tentato di avere lo stesso target Silverlight, WPF e potenzialmente UAP della DLL, è necessario compilare l'applicazione più volte con tag di compilazione diversi. La sfida con ciascuna delle piattaforme di cui sopra è che mentre condividono gli stessi concetti, le piattaforme sono sufficientemente diverse da dover aggirare quelle differenze. Qui è dove andiamo all'inferno #if
.
Questo approccio richiede anche la modifica manuale del .csproj
file per gestire i riferimenti dipendenti dalla piattaforma. Poiché il tuo .csproj
file è un file MSBuild, è del tutto possibile farlo in un modo noto e prevedibile.
#se diavolo
È possibile attivare e disattivare sezioni di codice utilizzando le #if
istruzioni in modo che sia efficace nel gestire le differenze minori tra le applicazioni. In apparenza sembra una buona idea. L'ho persino usato come mezzo per attivare e disattivare la visualizzazione del riquadro di selezione per eseguire il debug del codice di disegno.
Il problema numero 1 #if
è che nessuno dei codici disattivati viene valutato dal parser. È possibile che si verifichino errori di sintassi latenti o, peggio ancora, errori logici in attesa di ricompilare la libreria. Questo diventa ancora più problematico con il codice di refactoring. Qualcosa di semplice come rinominare un metodo o cambiare l'ordine dei parametri verrebbe normalmente gestito OK, ma poiché il parser non valuta mai nulla di spento #if
dall'istruzione improvvisamente hai un codice rotto che non vedrai fino a quando non ti ricompili.
Tutto il mio codice di debug che è stato scritto in quel modo ha dovuto essere riscritto dopo che una serie di refactoring lo hanno rotto. Durante la riscrittura, ho usato una classe di configurazione globale per attivare e disattivare quelle funzionalità. Ciò lo ha reso a prova di refactoring, ma tale soluzione non aiuta quando l'API è completamente diversa.
Il mio metodo preferito
Il mio metodo preferito, basato su molte lezioni dolorose apprese e persino basato sull'esempio di Microsoft, è di utilizzare più assiemi.
Un assembly NetStandard principale definirebbe tutte le interfacce e conterrebbe tutto il codice comune. Le implementazioni dipendenti dalla piattaforma si troverebbero in un assembly separato che aggiungerebbe funzionalità se incluse.
Questo approccio è esemplificato dalla nuova API di configurazione e dall'architettura di identità corrente. Poiché sono necessarie integrazioni più specifiche, è sufficiente aggiungere quei nuovi assiemi. Tali assiemi forniscono anche funzioni di estensione per incorporarsi nell'installazione. Se si utilizza un approccio di iniezione di dipendenza, questi metodi di estensione consentono alla libreria di registrare i propri servizi.
Questo è l'unico modo che conosco per evitare l' #if
inferno e soddisfare un ambiente sostanzialmente diverso.