У вас хороший вопрос. Вероятно, есть некоторые компромиссы с вашим решением. Окончательный ответ действительно зависит от того, что вы подразумеваете под зависимостью от платформы. Например, если вы запускаете процесс для запуска внешних приложений и просто переключаетесь между одним приложением, вы, вероятно, справитесь с этим без особых сложностей. Если вы говорите P / Invoke с нативными библиотеками, то нужно сделать еще немного. Однако, если вы связываетесь с библиотеками, которые существуют только на одной платформе, вам, вероятно, потребуется использовать несколько сборок.
Внешние приложения
Вам, вероятно, не нужно использовать #if
заявления в этой ситуации. Просто установите несколько интерфейсов и используйте одну реализацию для каждой платформы. Используйте фабрику, чтобы обнаружить платформу и предоставить правильный экземпляр.
В некоторых случаях это просто двоичный файл, скомпилированный для конкретной платформы, но имя исполняемого файла и все параметры определены одинаково. В этом случае это вопрос определения правильного исполняемого файла. Для приложения массового преобразования звука, которое может работать в Windows и Linux, у меня был статический инициализатор, разрешающий двоичное имя.
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);
}
}
Ничего особенного здесь. Просто хорошие старомодные занятия.
P / Invoke
P / Invoke немного сложнее. Суть в том, что вам нужно убедиться, что загружена правильная версия нативной библиотеки. На окнах вы бы P / Invoke SetDllDirectory()
. Разные платформы могут не нуждаться в этом шаге. Так что здесь все может стать грязным. Возможно, вам придется использовать #if
операторы, чтобы контролировать, какой вызов используется для управления разрешением пути к вашей библиотеке - особенно, если вы включаете его в свой пакет распространения.
Ссылки на совершенно разные платформенно-зависимые библиотеки
Здесь может пригодиться многоцелевой подход старой школы. Однако это приходит с большим количеством уродства. В те дни, когда некоторые проекты пытались использовать одну и ту же DLL-библиотеку для Silverlight, WPF и, возможно, UAP, вам приходилось компилировать приложение несколько раз с разными тегами компиляции. Проблема с каждой из вышеперечисленных платформ заключается в том, что, хотя они и используют одни и те же понятия, платформы достаточно различны, поэтому вам приходится обходить эти различия. Это где мы попадаем в ад #if
.
Этот подход также требует ручного редактирования .csproj
файла для обработки зависимых от платформы ссылок. Поскольку ваш .csproj
файл является файлом MSBuild, это вполне возможно сделать известным и предсказуемым образом.
# если ад
Вы можете включать и выключать разделы кода, используя #if
операторы, чтобы эффективно обрабатывать незначительные различия между приложениями. На первый взгляд это звучит как хорошая идея. Я даже использовал его как средство для включения и выключения визуализации ограничивающего прямоугольника для отладки кода чертежа.
Проблема номер 1 в #if
том, что ни один из отключенных кодов не анализируется анализатором. У вас могут быть скрытые синтаксические ошибки или, что еще хуже, логические ошибки, ожидающие перекомпиляции библиотеки. Это становится еще более проблематичным с рефакторингом кода. Что-то простое, например, переименование метода или изменение порядка параметров, обычно обрабатывается нормально, но поскольку анализатор никогда не оценивает ничего, отключенного #if
оператором, вы неожиданно испортили код, который не увидите, пока не перекомпилируете.
Весь мой код отладки, который был написан таким образом, должен был быть переписан после того, как ряд рефакторингов сломал его. Во время переписывания я использовал глобальный класс конфигурации для включения и выключения этих функций. Это сделало его надежным инструментом рефакторинга, но такое решение не помогает, когда API полностью отличается.
Мой предпочтительный метод
Мой предпочтительный метод, основанный на многих болезненных уроках и даже на собственном примере Microsoft, заключается в использовании нескольких сборок.
Одна базовая сборка NetStandard будет определять все интерфейсы и содержать весь общий код. Зависящие от платформы реализации были бы в отдельной сборке, которая добавляла бы функции, когда они включены.
Этот подход иллюстрируется новым API конфигурации и текущей архитектурой Identity. Поскольку вам нужны более конкретные интеграции, вы просто добавляете эти новые сборки. Эти сборки также предоставляют функции расширения для включения в ваши настройки. Если вы используете подход внедрения зависимостей, эти методы расширения позволяют библиотеке регистрировать свои сервисы.
Это единственный известный мне способ избежать #if
ада и удовлетворить существенно другую среду.