.NET Core Framework

Кросс-платформенная .NET Framework

Cheryl Simmons

Продукты и технологии:

Microsoft .NET Framework, Visual Studio

В статье рассматриваются:

  • совместное использование Portable Class Libraries (PCL) между платформами;
  • применение .NET Portability Analyzer;
  • выявление и уменьшение остроты проблем совместимости.

Большинство разработчиков предпочитает писать свой код бизнес-логики единожды, а затем повторно использовать его. Это гораздо проще, чем создавать разные приложения для каждой из нескольких платформ. Недавние объявления по поводу .NET Core — компонентной версии Microsoft .NET Framework — и о тесном партнерстве Microsoft и Xamarin означают, что если вы создаете Portable Class Libraries (PCL), совместимые с .NET Core, то вы ближе к этой реальности, чем когда-либо раньше.

Ну а что насчет ваших существующих библиотек .NET Framework? Сколько работы они потребуют, чтобы сделать их совместимыми с разными платформами и преобразовать их в PCL? Знакомьтесь с .NET Portability Analyzer. Применение нескольких простых приемов и внесение кое-каких изменений в файлы проектов поможет облегчить этот процесс.

Утилита .NET Portability Analyzer — это расширение Visual Studio, созданное группой .NET Framework. Вы можете использовать его с любой недавней версией Visual Studio, которая поддерживает расширения. Просто укажите в Portability Analyzer свои сборки или проекты, и эта утилита предоставит сводный детализированный отчет и рекомендации по API, которые вы должны задействовать для повышения совместимости. В случае проектов утилита перечисляет сообщения об ошибках и обеспечивает вам переход прямо к тем строкам кода, которые нужно изменить. Эта утилита также предоставляет результаты на ключевых платформах Microsoft, и вы можете сконфигурировать ее, чтобы получать результаты на других платформах, таких как Mono и Xamarin.

У .NET Portability Analyzer есть равноценная консольная версия — API Portability Analyzer (ее можно скачать по ссылке aka.ms/w1vcle), которая генерирует результаты, аналогичные сообщаемым .NET Portability Analyzer. В этой статье я сосредоточусь на использовании расширения Visual Studio. Кроме того, важно отметить, что за время между написанием статьи и ее публикацией вполне могли появиться какие-то обновления расширения .NET Portability Analyzer, поэтому внешний вид этой утилиты может отличаться от изображений, которые вы увидите здесь.

Установка на успех

Чтобы успешно портировать библиотеку на другие платформы, она должна быть хорошо проработана и содержать в основном бизнес-логику. UI-код следует выделять в другие проекты. Однако, поскольку .NET Core является подмножеством .NET Framework, даже при тщательной проработке кода ваши библиотеки могут использовать API, не поддерживаемые в .NET Core.

В некоторых случаях есть альтернативные API, которые делают то же самое. В этих случаях Portability Analyzer будет предлагать альтернативный API. В других случаях замены нет, и вам придется выделить специфичный для платформы код в отдельный проект. Наконец, даже если вы не имеете ни малейшего понятия, насколько хорошо проработана какая-то сборка, вы можете использовать Portability Analyzer для ее быстрой оценки.

Чтобы задействовать это расширение, вам потребуется Visual Studio 2013 или 2014. Следующий шаг — установить расширение. Вы найдете его поиском по .NET Portability Analyzer в Visual Studio Gallery или непосредственно на aka.ms/lah74y.

Щелкните кнопку Download и выберите Open. В следующем диалоговом окне выберите версию Visual Studio, к которой вы хотите применить это расширение. Щелкните Install, что приведет к запуску установки, а затем Close, чтобы закрыть диалог. Теперь вы готовы выбрать свои целевые платформы и проанализировать сборки или проекты.

Выберите свои целевые платформы

Portability Analyzer предоставляет по умолчанию результаты для .NET Framework, ASP.NET vNext (также называемой .NET Core), Windows и Windows Phone. Вы можете указать дополнительные варианты, обратившись ко входу .NET Portability Analyzer из Tools | Options в Visual Studio и выбрав набор платформ, на которые вы собираетесь ориентироваться (рис. 1).

Выбор целевых платформ для проекта
Рис. 1. Выбор целевых платформ для проекта

Запуск Portability Analyzer

Анализировать сборки и проекты можно двумя способами.

  • Для анализа сборок или исполняемых файлов запустите Portability Analyzer из меню Analyze в Visual Studio и перейдите в каталог с нужной сборкой. В этом случае утилита генерирует сводку (summary) и детализированный отчет.
  • Для анализа проектов щелкните правой кнопкой мыши целевой проект в Solution Explorer. Выберите Analyze | Analyze Assembly Portability (рис. 2), специфичный для указанного проекта. В этом случае утилита генерирует сводку, детализированный отчет и выводит сообщение в Error List, который показывает имя файла и номер строки, где обнаружена проблема. Дважды щелкая каждое сообщение, вы можете перемещаться на указанную строку кода.

Выбор Analyze Assembly Portability для конкретного проекта
Рис. 2. Выбор Analyze Assembly Portability для конкретного проекта

Чтобы проверить утилиту, я открыла проект, над которым работала год назад, — игру в слова, предназначенную для Windows Phone Silverlight 8. Я начала со своей прикладной логики в Portable Class Library (PCL), ориентированной на Windows Phone 8 и Windows 8. Идея заключалась в повторном использовании этой библиотеки для реализации того же приложения под Windows. Однако я наткнулась на некоторые проблемы и была занята другой работой, поэтому сменила подход, и теперь моя библиотека ориентирована только на Windows Phone Silverlight 8.

Мой план заключался в анализе портируемости библиотеки, внесении в код необходимых изменений, последующей конвертации проекта в проект PCL и его ориентации на Windows 8.1, Windows Phone 8.1 и Windows Phone Silverlight 8.1. Я также хотела проверить ее совместимость с .NET Core. Portability Analyzer должен дать мне представление о работе, которую нужно проделать для поставленных задач, без реального преобразования проекта, его переориентации и попытки рассортировать ошибки компиляции. Мне также было любопытно посмотреть, смогу ли я использовать эту библиотеку при создании приложения Android, поэтому я настроил утилиту на выдачу результатов и для Xamarin.Android.

Я прогнала утилиту, и результаты оказались обнадеживающими. На рис. 3 показаны сводка, детальный отчет, сообщения об ошибках и URL отчета. Согласно сводке, мою библиотеку можно считать вполне совместимой со всеми этими платформами. Она на 100% совместима с Windows Phone Silverlight 8.1, что не удивительно, поскольку Windows Phone Silverlight 8 изначально была целевой платформой.

Детальный отчет о совместимости, показывающий совместимые платформы
Рис. 3. Детальный отчет о совместимости, показывающий совместимые платформы

У .NET Portability Analyzer есть равноценная консольная версия — API Portability Analyzer.

Подробные результаты показывается по аналогии с электронной таблицей, и в них сообщается только о тех API, которые не поддерживаются одной или более целевыми платформами. Эти результаты легко просканировать. Если API не поддерживается, она помечается красным крестиком, а если поддерживается — зеленой галочкой. Важно отметить, что API, поддерживаемые на всех платформах, не требуют никакого рефакторинга и поэтому не включаются в этот отчет.

В отчет также включается столбец с рекомендуемыми изменениями, подсказывающими альтернативные API, которые будут работать на нескольких платформах. В нижней части отчета имеется ссылка Back to Summary. Она позволяет перейти обратно в сводку наверх. Мои результаты очень краткие, но в более длинных отчетах этот «возврат наверх» очень полезен.

Поскольку я анализировала проект, в моем отчете содержатся сообщения Error List, указывающие файл и номер строки, где встречается использование проблемной API. Если щелкнуть сообщение, утилита обеспечит переход в файл к строке, указанной в сообщении.

Если вы хотите обращаться к результатам вне Visual Studio, то они хранятся в HTML-файле (ApiPortabilityAnalysis.htm), который находится в том же каталоге проекта, что и целевая сборка. Этот каталог указывается строке URL вверху отчета (рис. 4).

Результаты анализа портируемости, хранящиеся для доступа извне Visual Studio
Рис. 4. Результаты анализа портируемости, хранящиеся для доступа извне Visual Studio

Работа продолжается

.NET Portability Analyzer, результаты и рекомендации этой утилиты, как ожидается, будут изменяться по мере того, как группа .NET будет собирать больше информации (рис. 5). Группа собирает анонимные данные об использовании API, когда вы работаете с утилитой или ее консольной версией, и суммируются на сайте по ссылке bit.ly/14vciaD.

На этом сайте показываются уровни использования .NET API
Рис. 5. На этом сайте показываются уровни использования .NET API

По умолчанию сайт перечисляет API, требующие наибольших изменений в коде. Вы можете указать другое значение в раскрывающемся списке Show (этот список не показан на снимке). Кроме того, можно задержать указатель мыши над значком «R» или «S» и увидеть те же рекомендации по коду, которые отображаются Portability Analyzer.

Имя каждого API в списке является ссылкой, которая переключает вас на страницу, сообщающую, на каких платформах поддерживается этот API. Группа планирует продолжить обновление сайта и, надеюсь, сделает возможным вклад со стороны заказчиков, так что периодически проверяйте этот сайт.

Преобразуем библиотеку в кросс-платформенную

Пора внести исправления в мою библиотеку. Я хочу, чтобы она работала с Windows Phone 8.1 и Windows 8.1, и вижу некоторые проблемы. К каждой из проблем я буду применять другой подход.

Использование абстракции платформы В списке есть две записи, относящиеся к потоку ресурса. Согласно отчету, эти API не поддерживаются в Windows 8.1 и Windows Phone 8.1. Утилита не рекомендует какой-либо API в качестве замены, поэтому придется удалить соответствующий код из моей библиотеки. Отчет сообщает, что эти два члена используются в нескольких строках кода, где загружается статический файл словаря:

WordList = new ObservableCollection<string>();
StreamResourceInfo info =
  Application.GetResourceStream(new
  Uri("/WordLibWP;component/US.dic", UriKind.Relative));
StreamReader reader = new StreamReader(info.Stream);
string line;
while ((line = reader.ReadLine()) != null)
{
  WordList.Add(line);
}
reader.close();

Я знаю, что можно использовать Windows.Storage API в Windows 8.1 и Windows Phone 8.1 для загрузки файла ресурсов, но тогда моя библиотека станет несовместимой с Windows Phone Silverlight. Для более широкого охвата я решила абстрагировать этот кусок специфичного для платформ кода. Абстракция платформы — полезный прием в предоставлении библиотеки, которая определяет поведение, но реализует это поведение по-разному для индивидуальных платформ. Если вы хотите, чтобы ваш код был совместим между платформами, вы должны быть знакомы с таким приемом.

Ниже перечислены основные шаги, предпринятые мной для достижения этой цели.

  • Определяем поведение в кросс-платформенной библиотеке: Для этого я создаю абстрактный класс в библиотеке, который определяет метод для чтения файла словаря. Я также определяю свойство, которое является экземпляром класса DictionaryReader. Нечто вроде следующего:
public abstract class DictionaryReader
{
  public abstract Task<ObservableCollection<string>>
    ReadDictionaryAsync(string path);
  public static DictionaryReader Instance { get; set; }
}
  • Реализуем поведение в специфичном для платформы коде: Для этого я наследую от класса DictionaryReader в проекте Windows Phone Silverlight. Я предоставляю реализацию метода ReadDictionaryAsync, который загружает и считывает файл словаря как ресурс приложения. Заметьте, что этот код, по сути, тот же, что и код в моей библиотеке, но имеет проверку ошибок для пути к ресурсу, так как для работы кода под Windows Phone требуется специфический формат. Мля реализация для других платформ будет отличаться в зависимости от методов чтения локального для приложения ресурса на этих платформах. Однако благодаря добавленной абстракции (рис. 6) это не должно быть проблемой.
  • Инициализируем определенный в библиотеке экземпляр для реализации, специфичной для платформы: Я добавляю код в класс App для своего проекта Windows Phone. Этот код инициализирует экземпляр DictionaryReader в реализации, специфичной для Windows Phone, который считывает файл как ресурс. И вновь этот код находится в проекте, специфичном для платформе, а не в анализируемом мной проекте:
public App()
{
  DictionaryReader.Instance = new DictionaryReaderPhone();
...
}

Рис. 6. Реализация загрузки и чтения словаря для Windows Phone

class DictionaryReaderPhone : DictionaryReader
{
  public override async Task<ObservableCollection<string>>
  ReadDictionaryAsync(string resourcePath)
{
  ObservableCollection<string> wordList =
    new ObservableCollection<string>();
  StreamResourceInfo info =
    Application.GetResourceStream(new Uri(resourcePath,
    UriKind.Relative));
  if (info == null)
    throw new ArgumentException("Could not load resource: " +
      resourcePath +
      ". For Windows Phone this should be in the
      Format:[project];component/[filename]");
  else
  {
    StreamReader reader = new StreamReader(info.Stream);
    string line;
    while ((line = await reader.ReadLineAsync()) != null)
    {
    wordList.Add(line);
    }
    reader.Close();
    return wordList;
    }
  }
}

Другой пример, демонстрирующий, как абстрагировать специфичный для платформы код, см. в статье MSDN Library «Platform Abstraction with the Portable Class Library» по ссылке aka.ms/q4sq7l.

Использование другого API Затем я получаю имя текущей культуры. Для этого я дважды щелкаю сообщение об ошибке и нахожу соответствующий метод:

public string CheckLanguage()
{
  return Thread.CurrentThread.CurrentCulture.Name.Split('-')[0];
}

Согласно Portability Analyzer, я могу использовать CultureInfo.CurrentCulture. Поскольку CurrentCulture — это статическое свойство CultureInfo, предоставляющее ту же информацию, которую я сейчас получаю от текущего потока, оно должно нормально работать. Я заменила код, полагающийся на получение класса Thread, следующим кодом, где используется CultureInfo:

public string CheckLanguage()
{
  return System.Globalization.CultureInfo.CurrentCulture.Name.Split('-')[0];
}

Пока все нормально

Я проверила свои изменения, и все выглядит неплохо. После этого я снова запустила Portability Analyzer. Полученный отчет оказался чистым, потому что нужно было внести в код лишь несколько изменений. Поэтому я решила добавить к своим исходным платформам поддержку Xamarin.Android, как показано на рис. 7.

Отчет при повторном анализе после изменений в коде
Рис. 7. Отчет при повторном анализе после изменений в коде

Последний шаг — преобразование проекта библиотеки из специфичного для Windows Phone в PCL, на которую можно ссылаться из множества приложений. Изначально я думала, что самый простой и быстрый способ сделать это — выполнить преобразование «по месту», вручную модифицируя файл проекта.

После исследований и попыток я пришла к выводу, что никаких значимых преимуществ в модификации файла проекта вручную нет. Для подстраховки я поговорила об этом с одним из членов группы .NET, и он согласился.

Без какого-то задела модификация вручную не имеет смысла. В зависимости от истории вашего проекта и других внесенных обновлений вы могли бы в конечном счете остаться с развалившимся проектом. Потратив время на преобразование проекта вручную, скорее всего вам потребуется создать новый проект PCL. Кроме того, даже если вручную преобразованный проект поначалу работает, он все равно может развалиться, когда вы впоследствии внесете другие изменения.

В итоге я создала новый проект PCL, чего и вам советую. Я скопировала свои файлы кода в новый проект. После этого я получила несколько ошибок компиляции из-за выражений using, которые больше не применялись в новой PCL. Я удалила их, и библиотека была готова к работе. Потом я переключилась на свое приложение Windows Phone Silverlight, чтобы указать ссылку на новую PCL, и оно тоже стало готовым к работе.

Заключение

Применение Portability Analyzer не только помогло мне быстро оценить работу, необходимую для преобразования библиотеки в кросс-платформенную, но и позволило выявить в коде все специфичные для платформ проблемы вплоть до вызовов методов и использования свойств. Кроме того, я получила предложения по API, для которых доступны альтернативы.

Я также показала вам методы выделения специфичного для платформы кода. Мои новые результаты от Portability Analyzer показали, что библиотека будет работать на дополнительных платформах, а также в Xamarin.Android. Наконец, я смогла создать новую PCL под новые целевые платформы и ссылаться на нее из существующего приложения. Новый .NET Portability Analyzer поможет вам в переносе ваших библиотек на новые платформы.


Cheryl Simmonsтехнический писатель в Microsoft в течение 10 лет, писала документацию и примеры кода для разработчиков по .NET Base Class Libraries, Windows Forms, Windows Presentation Foundation, Silverlight, Windows Phone и связанному с ними инструментарию.

Выражаю благодарность за рецензирование статьи экспертам Microsoft Мэтту Гэлбрейту (Matt Galbraith), Дэниелю Плейстиду (Daniel Plaisted) и Тэйлору Саутуику (Taylor Southwick).