Special Windows 10 issue 2015

Volume 30 Number 11

Microsoft .NET Framework - Разработка с использованием .NET и Universal Windows Platform

By Дэниэл Якобсон | Windows 2015

Продукты и технологии:
Microsoft .NET Framework, Visual Studio 2015, Universal Windows Platform, Windows 10 В статье рассматриваются:

  • разработка на Universal Windows Platform с применением новейшей технологии .NET;
  • изменения в NuGet и их связь с .NET Framework;
  • .NET Native — что это означает для разработчиков.

С появлением Visual Studio 2015 теперь можно использовать новейшую технологию .NET для создания приложений Universal Windows Platform (UWP), выполняемых на всех устройствах с Windows 10, в том числе на смартфоне, лэптопе или планшете, Surface Hub в вашем офисе, консоли Xbox, HoloLens и любых других устройствах, какие только можно вообразить в Интернете вещей (Internet of Things, IoT). Для Windows-разработчика настали по-настоящему интересные времена.

Что нового приносит UWP?

Как .NET-разработчик вы оцените все то, что предлагает UWP. UWP-приложения будут выполняться в режиме Windowed (оконном режиме) на огромном количестве настольных компьютеров, обновленных до Windows 10. UWP-приложения смогут достигать всех устройств с Windows 10 благодаря одному прикладному пакету и единой кодовой базе. Кроме того, UWP-приложения используют преимущества новой Microsoft .NET Core Framework (подробно поясняется далее в этой статье). Ваша бизнес-логика под .NET будет работать и на других платформах, поддерживающих .NET Core, таких как ASP.NET 5. При установке UWP-приложения развертывают малую копию .NET Core, поэтому такое приложение всегда будет работать с той версией .NET, в которой вы тестировали это приложение. Все .NET UWP-приложения в полной мере используют преимущества .NET Native, который генерирует высоко оптимизированный «родной» машинный код, обеспечивая выигрыш в производительности (также поясняется в этой статье).

.NET Core Framework

Это новая версия .NET — универсальная и модульная реализация Microsoft .NET Framework, которую можно портировать и использовать во множестве разных сред для самых разнообразных рабочих нагрузок. Кроме того, .NET Core Framework имеет открытый исходный код, доступна на GitHub (github.com/dotnet/corefx) и поддерживается Microsoft в Windows, Linux и Mac OS X. Если вы UWP-разработчик, использующий новейшую технологию .NET, эта инфраструктура даст вам колоссальные преимущества. В Visual Studio 2015 можно задействовать портируемые библиотеки классов (PCL) .NET Core, ориентированные на любые приложения UWP, .NET 4.6 или ASP.NET 5, даже если они кросс-платформенные.

Более того, .NET Core Framework является надмножеством .NET API, которые были ранее доступны при разработке приложений Windows Store. Это означает, что у UWP-разработчиков теперь в арсенале API появилось несколько дополнительных пространств имен. Одно из них — System.Net.Sockets, которое предназначено для коммуникаций по протоколу UDP. Ранее оно не было доступно в приложениях Windows Runtime (WinRT), и для использования WinRT-специфичных UDP API приходилось использовать обходные пути. Теперь, когда Sockets доступно в .NET Core, вы можете задействовать в своих UWP-приложениях тот же код сокетов, что и в других .NET-приложениях.

Другое преимущество в том, что System.Net.Http.HttpClient API встроен в стеки WinRT HTTP. Это позволяет использовать HTTP/2 по умолчанию, если сервер поддерживает его, что уменьшает задержки и общее количество полных обменов данными при взаимодействии.

Клиент Windows Communication Foundation (WCF) (и связанный с ним диалог Add Service Reference) был ранее недоступен в проектах .appx для Windows Phone, но теперь, поскольку он является частью .NET Core, его можно применять во всех .NET UWP-приложениях.

Наконец, .NET Core — это нижележащая инфраструктура, от которой зависит .NET Native. Когда проектировали .NET Native, стало понятно, что .NET Framework не подойдет в качестве фундамента для библиотек классов этой инфраструктуры. Дело в том, что .NET Native статически связывает инфраструктуру с приложением, а затем удаляет все лишнее, что не нужно приложению. (Здесь я сильно упрощаю общую картину, но идею вы уловили. Подробнее на эту тему см. «Inside .NET Native» по ссылке bit.ly/1UR7ChW.)

Традиционная реализация .NET Framework не предусматривает разбиения на модули, поэтому компоновщик (linker) не может включить в приложение лишь ту часть инфраструктуры, которая нужна приложению. Но .NET Core, по сути, является ответвлением .NET Framework, реализация которой оптимизирована с учетом модульности. Другое преимущество этой реализации — возможность поставлять .NET Core Framework как набор NuGet-пакетов, что позволяет вам обновлять индивидуальные классы вне самой .NET Framework. Однако, прежде чем двигаться дальше, давайте обсудим изменения в NuGet.

Что нового в NuGet?

В UWP встроена поддержка NuGet 3.1. В эту версию включены средства, которые улучшают управление зависимостями пакетов и локальное кеширование ваших пакетов для повторного использования в нескольких проектах.

В NuGet 3.1 обновлена модель объявления зависимостей пакетов. Начиная с ASP.NET 5, в NuGet введена поддержка файла project.json, и UWP поддерживает ту же модель. Project.json позволяет вам описывать зависимости проекта с помощью четких определений пакетов, от которых прямо зависит ваш проект. Поскольку формат одинаков и для ASP.NET 5, и для UWP, один и тот же файл можно использовать для определения ссылок пакета на обеих платформах, а также для PCL.

Смена packages.config на project.json позволяет «перезагружать» ссылки в проектах, и теперь есть новая транзитивная зависимость, поддерживаемая NuGet. Раньше, когда вы ссылались на пакет, который ссылался на другой NuGet-пакет, управлять версиями пакета было весьма затруднительно. Например, NHibernate — это пакет, зависимый от lesi.collections. В packages.config было бы две ссылки — по одной на каждый пакет. Когда вы хотите обновить NHibernate, обновляете ли вы и lesi.collections? Или, если появится обновление для lesi.collections, должны ли вы обновить и NHibernate для поддержки новых средств? Все это запросто превращается в запутанный цикл, из-за которого сложно управлять версиями пакетов. Функциональность транзитивных зависимостей (transitive dependencies feature) NuGet абстрагирует это решение для обновления ссылок пакета с использованием улучшенной семантики контроля версий в файлах определения пакетов (nuspecs).

Кроме того, NuGet теперь скачивает и сохраняет копии используемых вами пакетов в папке глобальных пакетов — %userprofile%\.nuget\packages. Это не только повышает эффективность вашего труда, потому что от вас требуется лишь раз скачать каждый пакет, но и уменьшает дисковое пространство, занимаемое пакетами на вашей рабочей станции, поскольку от проекта к проекту используются одни и те же двоичные файлы пакетов.

NuGet и .NET Core

Что будет, когда вы скомбинируете модульную функциональность .NET Core с системой управления зависимостями пакетов в NuGet 3.1? Вы получите возможность обновлять индивидуальные пакеты .NET Framework вне зависимости от остальной .NET Framework. В случае UWP инфраструктура .NET Core включается как набор NuGet-пакетов в ваше приложение. При создании нового проекта вы увидите только универсальную зависимость пакетов от Microsoft.NETCore.UniversalWindowsPlatform, но, изучив этот пакет в NuGet, вы обнаружите все библиотеки .NET Framework, которые были включены, как показано на рис. 1.

Просмотр библиотек .NET Framework в NuGet
Рис. 1. Просмотр библиотек .NET Framework в NuGet

Например, появляется обновление класса System.Net.Sockets, вводящее новый API, который вы хотели бы задействовать в своем приложении. В традиционной .NET вашему приложению пришлось бы принять зависимость от новой сборки всей .NET Framework. В случае UWP и .NET Core с NuGet вы можете обновить свои NuGet-зависимости для включения новейшей версии лишь этого пакета. Затем, когда ваше приложение скомпилировано и упаковано, эта версия библиотеки инфраструктуры будет включена в ваше приложение. Это дает вам гибкость в использовании новейшей технологии .NET, не заставляя пользователей всегда устанавливать на их устройства новейшую версию всей инфраструктуры.

В дополнение к возможности обновления .NET-классов по выбору компонентная природа .NET Core также делает возможным поддержку .NET Native, что обеспечивает для всех приложений Windows 10, написанных на C# и Visual Basic, выигрыш в производительности после установки на потребительские устройства.

Что такое .NET Native?

Теперь, когда вы знаете, что .NET Core Framework обеспечивает поддержку .NET Native, я подробно объясню, что такое .NET Native и чем он полезен для вас как для разработчика на платформе UWP.

.NET Native — процесс заблаговременной компиляции (ahead-of-time, AOT) — преобразует ваш управляемый .NET-код в «родной» машинный код на этапе компиляции. В противоположность этому, традиционная .NET использует компиляцию по запросу (just-in-time, JIT), при которой компиляция какого-либо метода в «родной» код откладывается до тех пор, пока он не будет впервые вызван в период выполнения. .NET Native в большей мере похож на компилятор C++. По сути, он использует компилятор C++ из Visual Studio как часть своей цепочки инструментария. Каждое управляемое (написанное на C# или Visual Basic) UWP-приложение будет использовать эту новую технологию. Приложения автоматически компилируются в «родной» код до того, как они попадают на потребительские устройства. Если вы хотите глубже изучить, как это работает, настоятельно рекомендую прочитать в MSDN Library статью «Compiling Apps with .NET Native» (bit.ly/1QcTGxm).

Как .NET Native влияет на вашу работу и на ваше приложение?

Результаты вашей работы скорее всего будут варьироваться, но в большинстве случаев ваше приложение будет запускаться и выполняться быстрее, а также использовать меньше системных ресурсов. Вы можете ожидать до 60% прироста скорости первого запуска приложения и до 40% при последующих запусках («теплых» запусках). Ваши приложения будут потреблять меньше памяти после компиляции в «родной» машинный код. Все зависимости от исполняющей среды .NET удаляются, поэтому конечным пользователям никогда не придется прерывать установку вашего приложения для получения специфической версии .NET Framework, на которую оно ссылается. По сути, все зависимости от .NET упаковываются в ваше приложение, поэтому его поведение не должно меняться просто потому, что на машину устанавливается другая версия .NET Framework.

Хотя ваше приложение компилируется в неуправляемые двоичные файлы, вы все равно можете использовать преимущества .NET-языков, к которым вы привыкли (C# или Visual Basic), и превосходные инструменты, связанные с ними. Наконец, вы можете по-прежнему применять всеобъемлющую и согласованную модель программирования, доступную благодаря .NET Framework с богатыми API для прикладной логики, встроенного управления памятью и обработки исключений.

За счет .NET Native вы получаете лучшее из двух миров: управляемой разработки и C++ с его колоссальной производительностью. Неплохо, да?

Конфигурация компиляции Debug и Release

Компиляция через .NET Native — сложный процесс, немного медленнее традиционной в .NET компиляции. Преимущества, упомянутые чуть ранее, даются ценой увеличения времени компиляции. Вы могли бы выбрать компиляцию в «родной» код всякий раз, когда запускается приложение, но тогда вы тратили бы дополнительное время, ожидая окончания сборки. Инструментарий Visual Studio предназначен для устранения этой проблемы и создания максимально комфортных для разработчиков условий.

Скомпилировав и запустив программу в конфигурации Debug, вы выполняете код Intermediate Language (IL) в CoreCLR, упакованной вместе с приложением. Системные сборки .NET упаковываются наряду с кодом вашего приложения, и это приложение получает зависимость от пакета Microsoft.NET.CoreRuntime (CoreCLR). Если инфраструктура CoreCLR отсутствует на устройстве, где вы тестируете, Visual Studio автоматически обнаружит это и установит ее до развертывания вашего приложения.

Это означает, что получаете максимум удобств при разработке: быструю компиляцию и установку, богатые средства отладки и диагностики и весь инструментарий, к которому вы привыкли при .NET-разработке.

Когда вы переключаетесь в режим Release, ваше приложение по умолчанию использует цепочку инструментария .NET Native. Поскольку пакет компилируется в неуправляемый двоичный код, в этом пакете не нужны библиотеки .NET Framework. Более того, пакет зависим от новейшей установленной версии исполняющей среды .NET Native в противоположность пакету CoreCLR. Исполняющая среда .NET Native на устройстве будет всегда совместима с пакетом вашего приложения.

Локальная компиляция в «родной» (неуправляемый) код через конфигурацию Release позволит протестировать ваше приложение в среде, похожей на ту, с которой имеют дело потребители. Важно тестировать это на регулярной основе по мере продвижения в разработке! Тестируя приложение с применением генерации кода и исполняющей среды, вы будете уверены в том, что отловили все возможные ошибки (например, условия потенциальных гонок).

Хорошее эмпирическое правило — тестировать приложение таким способом периодически на протяжении всей разработки, чтобы своевременно выявлять и исправлять любые проблемы, которые могут исходить от компилятора .NET Native. В большинстве случаев никаких проблем возникать не должно, но пока еще остаются шероховатости, с которыми нужно быть осторожными при работе с .NET Native. Один из примеров — массивы с размерностью более четырех. Ваши потребители будут получать скомпилированную .NET Native версию приложения, поэтому всегда важно тестировать эту версию на протяжении всей разработки и до поставки.

В дополнение к тестированию компиляции через .NET Native вы, вероятно, также заметили, что конфигурация AnyCPU исчезла. В случае .NET Native конфигурация AnyCPU не является допустимой, поскольку компиляция в «родной» машинный код зависима от архитектуры процессора. Дополнительное следствие этого заключается в том, что при упаковке приложения вы должны выбирать все три архитектурные конфигурации (x86, x64 и ARM), чтобы приложение могло работать на максимально широком спектре устройств. В конце концов, это Universal Windows Platform.

Учитывая сказанное, вы можете по-прежнему компилировать библиотеки и DLL в конфигурации AnyCPU, чтобы ссылаться на них в своем UWP-приложении. Эти компоненты будут компилироваться в специфичные для архитектуры процессора двоичные файлы на основе конфигурации проекта (.appx), использующего их.

.NET Native в облаке

Одна из замечательных особенностей .NET Native в том, что этот компилятор можно разместить в облаке. То есть, когда в компилятор вносятся какие-либо усовершенствования, которые могут положительно повлиять на ваше приложение, компилятор .NET Native, размещенный в облаке Store, позволяет перекомпилировать пакет вашего приложения для использования этих улучшений. Всякий раз, когда выполняется такая компиляция, она прозрачна для вас как для разработчика, но в конечном счете сделает счастливее ваших пользователей.

Однако это может оказать некоторое воздействие на ваш рабочий процесс. Например, будет хорошей идеей всегда проверять, что у вас установлены новейшие версии инструментов, чтобы вы могли тестировать компиляцию своего приложения через .NET Native с использованием самой новой локальной версии компилятора. Кроме того, когда вы компилируете пакет Store в Visual Studio, создаются два пакета: один с расширением .appxupload, а другой («тестовый») с расширением .appx для развертывания. В пакете .appxupload содержатся двоичные MSIL-файлы, а также явные ссылки на версию цепочки инструментария .NET Native, используемой вашим приложением (указываемые в AppxManifest.xml как ilc.exe). Затем этот пакет отправляется в Store и компилируется с применением ровно той же версии цепочки инструментария .NET Native. Поскольку компилятор находится в облаке, он может итеративно исправлять ошибки, не требуя от вас локальной перекомпиляции приложения.

В случае .NET Native вы должно быть осторожны в том, какой пакет вы закачиваете в Store. Поскольку Store выполняет компиляцию в машинный код за вас, вы не должны загружать «родные» двоичные файлы, сгенерированные локальным компилятором .NET Native. Рабочий процесс Visual Studio поможет вам пройти эту процедуру и выбрать правильный пакет. Полное описание создания пакета для Store см. в MSDN Library в статье «Packaging Universal Windows Apps for Windows 10» (bit.ly/1OQTTG0).

Отладка с помощью .NET Native

Если вы нашли в приложении проблемы, которые, как вы подозреваете, вызваны .NET Native, есть метод, помогающий в отладке. Конфигурации Release полностью оптимизируют код по умолчанию (например, во многих местах применяется подстановка кода), что приводит к потере некоторых артефактов, важных для отладки. В итоге попытка отладки в конфигурации Release может оказаться затруднительной: не исключены непредсказуемое поведение при пошаговом выполнении кода и достижении точек прерывания, а также невозможность просмотра переменных из-за оптимизации памяти. Поскольку исходное поведение конфигураций Release — применение компилятора .NET Native с оптимизацией кода, отладить какую-либо проблему, которая является следствием процесса компиляции .NET Native, крайне сложно.

Хороший обходной способ — создать собственную конфигурацию компиляции для проекта, при которой используется компилятор .NET Native, но полная оптимизация кода не выполняется. Чтобы создать такую конфигурацию, откройте Configuration Manager в раскрывающемся списке конфигураций сборки ( рис. 2).

Открытие Configuration Manager
Рис. 2. Открытие Configuration Manager

В раскрывающемся списке Active solution configuration выберите <New…>, чтобы создать новую конфигурацию, как показано на рис. 3.

Creating a New Configuration
Рис. 3. Создание новой конфигурации

Присвойте новой конфигурации имя, которое будет полезно и впоследствии. Я предпочитаю имя «Debug .NET Native». Скопируйте настройки из конфигурации Release, а затем щелкните OK.

Закройте Configuration Manager и откройте страницу свойств проекта, щелкнув правой кнопкой мыши этот проект в Solution Explorer и выбрав Properties. Перейдите на вкладку Build и убедитесь, что флажок Compile with .NET Native tool chain установлен, а флажок Optimize code сброшен (рис. 4).

Создание конфигурации сборки для отладки .NET Native
Рис. 4. Создание конфигурации сборки для отладки .NET Native

Теперь у вас есть конфигурация, которую можно использовать для отладки проблем, специфичных для .NET Native.

Подробнее об отладке с помощью .NET Native см. в MSDN Library статью «Debugging .NET Native Windows Universal Apps» bit.ly/1Ixd07v.

Анализатор .NET Native

OКонечно, хорошо знать, как отлаживать проблемы, но не лучше ли избегать их с самого начала? В ваше приложение можно установить Microsoft.NETNative.Analyzer (bit.ly/1LugGnO) через NuGet. Из Package Manager Console вы можете установить этот пакет следующей командой: Install-Package Microsoft.NETNative.Analyzer. При разработке этот анализатор будет выдавать предупреждения, если ваш код окажется несовместимым с компилятором .NET Native. На поверхности .NET есть небольшой участок, который будет несовместим с этим компилятором, но в большинстве проблем это никогда не вызовет никаких проблем.

Заключение

Как видите, для .NET-разработчиков под Windows наступили интересные времена. С появлением UWP, .NET Native и изменений в NuGet создание приложений для такого большого количества устройств упростилось как никогда раньше. Впервые вы можете задействовать преимущества новейших достижений в каком-либо .NET-классе и все равно нормально выполнять свое приложение на всех устройствах под управлением Windows 10.


Дэниэл Якобсон (Daniel Jacobson) — менеджер программ по Visual Studio, работает над инструментарием для разработчиков на платформе Windows. С ним можно связаться по адресу dajaco@microsoft.com.

Выражаю благодарность за рецензирование статьи экспертам Кино Агилере (Kino Aguilar), Адаму Деннингу (Adam Denning), Ишаю Галатцеру (Yishai Galatzer), Дженни Хейз (Jenny Hayes), Джереми Менгу (Jeremy Meng), Харикришне Менону (Harikrishna Menon), Джессике Принс (Jessica Prince), Унни Равиндранатану (Unni Ravindranathan), Навиту Саксене (Navit Saxena), Майклу Стреховски (Michael Strehovsky), Дебби Торн (Debbie Thorn), Мэтью Уалду (Matthew Whilde), Лусиану Вишику (Lucian Wischik).