Masz dobre pytanie Prawdopodobnie występują pewne kompromisy z twoim rozwiązaniem. Ostateczna odpowiedź naprawdę zależy od tego, co rozumiesz przez zależność od platformy. Na przykład, jeśli uruchamiasz proces uruchamiania aplikacji zewnętrznych i po prostu przełączasz się między aplikacjami, prawdopodobnie możesz sobie z tym poradzić bez zbytniej komplikacji. Jeśli rozmawiasz w trybie P / Invoke z bibliotekami natywnymi, musisz zrobić coś więcej. Jednak jeśli łączysz się z bibliotekami, które istnieją tylko na jednej platformie, prawdopodobnie będziesz musiał użyć wielu zestawów.
Aplikacje zewnętrzne
W #if
tej sytuacji prawdopodobnie nie będziesz musiał używać instrukcji. Wystarczy skonfigurować kilka interfejsów i mieć jedną implementację na platformę. Użyj fabryki, aby wykryć platformę i zapewnić odpowiednią instancję.
W niektórych przypadkach jest to tylko plik binarny skompilowany dla konkretnej platformy, ale nazwa pliku wykonywalnego i wszystkie parametry są takie same. W takim przypadku chodzi o rozwiązanie właściwego pliku wykonywalnego. W przypadku masowej aplikacji konwertera audio, która może działać w systemie Windows i Linux, miałem statyczny inicjator rozpoznający nazwę binarną.
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);
}
}
Nic szczególnego tutaj. Po prostu dobre klasyczne klasy.
P / Invoke
P / Invoke jest nieco trudniejsze. Najważniejsze jest to, że musisz upewnić się, że załadowana jest odpowiednia wersja biblioteki natywnej. W systemie Windows P / Invoke SetDllDirectory()
. Różne platformy mogą nie wymagać tego kroku. Więc tutaj może być bałagan. Konieczne może być użycie #if
instrukcji do kontrolowania, które wywołanie służy do kontrolowania rozwiązywania ścieżki biblioteki - szczególnie jeśli dołączasz ją do pakietu dystrybucyjnego.
Łączenie z całkowicie różnymi bibliotekami zależnymi od platformy
Przydaje się tutaj oldschoolowe podejście ukierunkowane na wiele celów. Jednak wiąże się to z dużą brzydotą. W czasach, gdy niektóre projekty próbowały mieć ten sam docelowy DLL DLL Silverlight, WPF i potencjalnie UAP, musiałbyś skompilować aplikację wiele razy z różnymi znacznikami kompilacji. Wyzwanie w przypadku każdej z powyższych platform polega na tym, że chociaż mają one te same koncepcje, platformy są wystarczająco różne, aby obejść te różnice. Właśnie tam wchodzimy do piekła #if
.
To podejście wymaga także ręcznej edycji .csproj
pliku w celu obsługi referencji zależnych od platformy. Ponieważ .csproj
plik jest plikiem MSBuild, można to zrobić w znany i przewidywalny sposób.
# jeśli do diabła
Możesz włączać i wyłączać sekcje kodu za pomocą #if
instrukcji, dzięki czemu skutecznie radzi sobie z niewielkimi różnicami między aplikacjami. Na powierzchni brzmi jak dobry pomysł. Użyłem go nawet do włączania i wyłączania wizualizacji ramki granicznej w celu debugowania kodu rysunkowego.
Problem z numerem 1 #if
polega na tym, że żaden z wyłączonych kodów nie jest analizowany przez analizator składni. Być może masz ukryte błędy składniowe lub, co gorsza, błędy logiczne czekające na ponowne skompilowanie biblioteki. Staje się to jeszcze bardziej problematyczne z kodem refaktoryzacji. Coś tak prostego jak zmiana nazwy metody lub zmiana kolejności parametrów normalnie byłaby obsługiwana OK, ale ponieważ parser nigdy nie ocenia niczego wyłączonego przez #if
instrukcję, nagle złamałeś kod, którego nie zobaczysz, dopóki nie skompilujesz ponownie.
Cały mój kod debugowania, który został napisany w ten sposób, musiał zostać przepisany po serii przeróbek. Podczas przepisywania użyłem globalnej klasy config do włączania i wyłączania tych funkcji. Dzięki temu refaktoryzuje narzędzie, ale takie rozwiązanie nie pomaga, gdy interfejs API jest zupełnie inny.
Moja preferowana metoda
Moją preferowaną metodą, opartą na wielu bolesnych doświadczeniach, a nawet opartą na własnym przykładzie Microsoftu, jest użycie wielu zestawów.
Jeden podstawowy zestaw NetStandard zdefiniowałby wszystkie interfejsy i zawierałby cały wspólny kod. Implementacje zależne od platformy byłyby w osobnym zestawie, który dodawałby funkcje, jeśli byłyby dołączone.
Przykładem takiego podejścia jest nowy interfejs API konfiguracji i obecna architektura tożsamości. Ponieważ potrzebujesz bardziej szczegółowych integracji, po prostu dodajesz te nowe zespoły. Zespoły te zapewniają również funkcje rozszerzeń, aby włączyć się do konfiguracji. Jeśli używasz metody wstrzykiwania zależności, te metody rozszerzeń pozwalają bibliotece zarejestrować swoje usługi.
Jest to jedyny znany mi sposób na uniknięcie #if
piekła i zaspokojenie zupełnie innego środowiska.