Edición especial de Windows 10 de 2015

Volumen 30, número 11

Microsoft .NET: .NET y el desarrollo para la Plataforma universal de Windows

Por Daniel Jacobson | Windows 2015

Ahora, con Visual Studio 2015, puede usar la tecnología .NET más reciente para crear aplicaciones para la Plataforma universal de Windows (UWP) que se ejecuten en todos los dispositivos con Windows 10, incluido el teléfono que tiene en el bolsillo, el portátil o tableta que lleva en la mochila, el Surface Hub de la oficina, la consola Xbox de casa, HoloLens o cualquier otro dispositivo que pueda imaginar en el Internet de las cosas (IoT). Es un momento realmente emocionante para ser desarrollador de Windows.

Novedades de UWP

Como desarrollador de .NET, valorará lo que le ofrece UWP. Las aplicaciones de UWP se ejecutarán en modo de ventana en la enorme cantidad de equipos de escritorio que se han actualizado y se actualizarán a Windows 10. Las aplicaciones de UWP podrán llegar a todos los dispositivos con Windows 10 que dispongan de un paquete de aplicaciones y un código base. Además, las aplicaciones de UWP aprovechan el nuevo Microsoft .NET Core Framework (como se explica en detalle más adelante). La lógica de negocios .NET se puede ejecutar en otras plataformas compatibles con .NET Core, como ASP.NET 5. Las aplicaciones de UWP implementan una pequeña copia de .NET Core con la aplicación, de modo que la aplicación siempre se ejecuta con la versión de .NET con la que se probó. Todas las aplicaciones de UWP de .NET aprovechan al máximo .NET Native, que genera un código máquina nativo altamente optimizado que mejora el rendimiento (lo que también se explica en este artículo).

.NET Core Framework

.NET Core Framework es una versión nueva de .NET para cargas de trabajo en la nube y dispositivos modernos. Es una implementación general y modular de Microsoft .NET Framework que se puede portar y usar en muchos entornos distintos para una gran variedad de cargas de trabajo. Además, .NET Core Framework es de código abierto, está disponible en GitHub (github.com/dotnet/corefx) y es compatible con Microsoft en Windows, Linux y Mac OS X. Si es desarrollador de UWP y usa la tecnología .NET más reciente, esto le ofrece grandes ventajas. En Visual Studio 2015, puede usar bibliotecas de clases portables (PCL) de .NET Core para cualquier aplicación de UWP, .NET 4.6 o ASP.NET 5, incluso las multiplataforma.

Además, .NET Core Framework es un superconjunto de las API de .NET disponibles previamente para el desarrollo de aplicaciones de la Tienda Windows. Esto significa que, ahora, los desarrolladores de UWP tienen varios espacios de nombres adicionales disponibles en su arsenal de API. Uno de estos espacios de nombres es System.Net.Sockets, que se usa para la comunicación de UDP. Antes, esta opción no estaba disponible en las aplicaciones de Windows Runtime (WinRT) y la solución consistía en usar las API de UDP específicas de WinRT. Ahora que hay sockets disponibles en .NET Core, puede usar el mismo código de socket en las aplicaciones de UWP y otras aplicaciones de .NET.

Otra ventaja es que la API System.Net.Http.HttpClient se basa en pilas HTTP de WinRT. Esto permite usar HTTP/2 de forma predeterminada si el servidor lo admite. Como resultado, se obtiene una latencia inferior y menos comunicaciones de ida y vuelta.

Antes, el cliente de Windows Communication Foundation (WCF), y el cuadro de diálogo asociado Agregar referencia de servicio, no estaban disponibles en los proyectos .appx de Windows Phone, pero, dado que forma parte de .NET Core, todas las aplicaciones de UWP de .NET lo pueden usar.

Finalmente, .NET Core es el marco de trabajo subyacente del que depende .NET Native. Cuando se diseñó .NET Native, estaba claro que .NET Framework no funcionaría como base para las bibliotecas de clases de marcos de trabajo. El motivo es que .NET Native vincula de forma estática el marco de trabajo a la aplicación y, a continuación, quita todo lo que la aplicación no necesita. (Esto es simplificar mucho, pero se entiende la idea. Para obtener más información, consulte “Inside .NET Native” (En el interior de .NET Native) en bit.ly/1UR7ChW).

La implementación tradicional de .NET Framework no está factorizada, de modo que, para el enlazador, reducir la cantidad del marco de trabajo que se compila en la aplicación supone todo un reto. Después de todo, .NET Core es, básicamente, una bifurcación de .NET Framework, cuya implementación se optimiza alrededor de cuestiones de factorización. Otra ventaja de esta implementación es la capacidad de enviar .NET Core Framework como conjunto de paquetes NuGet, de modo que se pueden actualizar las clases individuales independientemente de .NET Framework. Antes de continuar, hablemos de los cambios de NuGet.

Novedades de NuGet

Con UWP, se incorpora la compatibilidad con NuGet 3.1. En esta versión, se incluyen características que mejoran la administración de dependencias de paquetes y un almacenamiento en caché local de los paquetes para reutilizarlos en distintos proyectos.

Con NuGet 3.1, el modelo de declaración de dependencia de paquetes se ha actualizado. A partir de ASP.NET 5, NuGet incorporó la compatibilidad con el archivo project.json, y este es el mismo modelo que admite UWP. Project.json permite describir las dependencias de un proyecto con una definición clara de los paquetes de los que usted depende inmediatamente. Dado que el formato es el mismo para ASP.NET 5 y UWP, puede usar el mismo archivo para definir referencias de paquetes para ambas plataformas, así como PCL.

Cambiar de packages.config a project.json le permite “reiniciar” las referencias de los proyectos, y ahora hay una nueva funcionalidad de dependencia transitiva de NuGet. Antes, la administración de versiones de paquetes solía ser difícil si se hacía referencia a un paquete que, a su vez, hacía referencia a otro paquete NuGet. Por ejemplo, NHibernate es un paquete que depende de lesi.Collections. En packages.config, tendría dos referencias, una para cada paquete. Si quiere actualizar NHibernate, ¿también actualiza lesi.collections? O, si hay una actualización para lesi.collections, ¿también debe actualizar NHibernate para admitir las características nuevas? Se convertía en un ciclo desorganizado y la administración de versiones de paquetes era difícil. La característica de dependencias transitivas de NuGet sintetiza esta decisión de actualizar referencias a paquetes con versiones de semántica mejorada en los archivos de definición de paquetes (nuspecs).

Además, ahora NuGet descarga y almacena una copia de los paquetes que se usan en una carpeta de paquetes global ubicada en la carpeta %perfildeusuario%\.nuget\packages. Esto no solo mejora el rendimiento de las referencias a paquetes al tener que descargar cada paquete solo una vez, sino que también reduce el espacio en disco que se usa en la estación de trabajo, ya que puede compartir los mismos archivos binarios de paquete entre proyectos.

NuGet y .NET Core

¿Qué sucede si se combina .NET Core, centrado en la factorización, con la administración con dependencias de paquetes de NuGet 3.1? Obtiene la capacidad de actualizar paquetes individuales de .NET Framework independientemente del resto de .NET Framework. Con UWP, .NET Core se incluye como conjunto de paquetes NuGet en la aplicación. Al crear un proyecto nuevo, verá solo la dependencia de paquete Microsoft.NETCore.UniversalWindowsPlatform general. Sin embargo, si observa el paquete en NuGet, verá todas las bibliotecas de .NET Framework incluidas, como se muestra en la Ilustración 1.

Visualización de bibliotecas de .NET Framework en NuGet
Ilustración 1 Visualización de bibliotecas de .NET Framework en NuGet

Por ejemplo, supongamos que hay una actualización para la clase System.Net.Sockets que incorpora una API nueva que quiere usar en su aplicación. Con .NET tradicional, la aplicación necesitaría usar una dependencia de una compilación nueva de todo .NET Framework. Con UWP y .NET Core con NuGet, puede actualizar las dependencias de NuGet para incluir solo la última versión de ese paquete. A continuación, la aplicación se compila y empaqueta y se incluye en la aplicación dicha versión de la biblioteca del marco de trabajo. Esto le ofrece la flexibilidad necesaria para usar la mejor y más reciente tecnología de .NET, sin obligar a los usuarios a tener siempre el marco de trabajo más reciente instalado en sus dispositivos.

Además de poder actualizar las clases de .NET a su propio ritmo, la naturaleza basada en componentes de .NET Core también habilita .NET Native, lo que ofrece beneficios de rendimiento para todas las aplicaciones de C# y Visual Basic para Windows 10 que se ejecutan en dispositivos de consumidor.

¿Qué es .NET Native?

Ahora que ya sabe que .NET Core Framework habilita .NET Native, explicaré detalladamente qué es y cómo le ayuda como desarrollador de UWP.

.NET Native es un proceso de compilación Ahead-Of-Time (AOT) que transforma el código de .NET administrado en código máquina nativo durante la compilación. En cambio, .NET tradicional usa la compilación Just-In-Time (JIT), que pospone la compilación nativa de un método hasta su primera ejecución en tiempo de ejecución. .NET Native es más similar a un compilador de C++. De hecho, usa el compilador de Visual Studio C++ como parte de su cadena de herramientas. Todas las aplicaciones universales de Windows administradas (C# o Visual Basic) usarán esta tecnología nueva. Las aplicaciones se compilan automáticamente en código nativo antes de llegar a los dispositivos de consumidor. Para obtener información más detallada sobre su funcionamiento, le recomiendo que lea el artículo de MSDN Library “Compilar aplicaciones con .NET Native”, disponible en bit.ly/1QcTGxm.

¿Cuál es el impacto de .NET Native sobre usted y su aplicación?

Probablemente, su uso variará, pero, en la mayoría de casos, la aplicación se iniciará más rápidamente, tendrá un rendimiento mejor y consumirá menos recursos del sistema. Cabe esperar una mejora del rendimiento de hasta un 60 por ciento en las aplicaciones la primera vez que se inician, y de hasta un 40 por ciento en los tiempos de inicio posteriores (inicio “semiactivo”). Las aplicaciones consumirán menos memoria cuando se compilen de forma nativa. Se quitarán todas las dependencias del tiempo de ejecución de .NET, de modo que los usuarios finales nunca tendrán que interrumpir su experiencia de configuración para adquirir la versión específica de .NET Framework a la que hace referencia la aplicación. De hecho, la aplicación incluye todas las dependencias de .NET, de modo que el comportamiento de la aplicación no debe cambiar solo porque haya un cambio en la versión de .NET Framework instalada en la máquina.

Aunque la aplicación se compila en archivos binarios nativos, puede aprovechar igualmente los lenguajes de .NET con los que está familiarizado (C# o Visual Basic) y las excelentes herramientas asociadas. Finalmente, puede seguir usando el modelo de programación completo y coherente disponible con .NET Framework, con una gran variedad de API para la lógica de negocios, la administración de memoria integrada y el control de excepciones.

Con .NET Native, tiene lo mejor de ambos mundos: desarrollo administrado con rendimiento de C++. ¿No es genial?

Comparación de las configuraciones Debug y Release-Compile

La compilación de .NET Native es un proceso complejo, lo que la hace un poco más lenta que la compilación clásica de .NET. Los beneficios mencionados anteriormente conllevan un coste de tiempo de compilación. Puede compilar de forma nativa cada vez que quiera ejecutar la aplicación, pero la compilación tardará más tiempo en completarse. Las herramientas de Visual Studio se han diseñado para corregir este problema y ofrecer la mejor experiencia de desarrollo posible.

Al compilar y ejecutar una configuración “Debug”, ejecuta un código de lenguaje intermedio en el CoreCLR que se incluye con la aplicación. Los ensamblados de sistema de .NET se empaquetan junto con el código de la aplicación, y la aplicación usa una dependencia del paquete Microsoft.NET.CoreRuntime (CoreCLR). Si falta el marco de trabajo de CoreCLR en el dispositivo en el que hace pruebas, Visual Studio lo detectará automáticamente y lo instalará antes de implementar la aplicación.

Esto significa que tendrá la mejor experiencia de desarrollo posible: una compilación e implementación rápidas, una depuración y diagnóstico avanzados y todas las herramientas a las que está acostumbrado con el desarrollo de .NET.

Al cambiar al modo “Release”, la aplicación usa la cadena de herramientas de .NET Native de forma predeterminada. Dado que el paquete se compila en archivos binarios nativos, no necesita contener las bibliotecas de .NET Framework. Además, el paquete depende del último runtime de .NET Native instalado, y no del paquete de CoreCLR. El runtime de .NET Native del dispositivo siempre será compatible con el paquete de aplicación.

La compilación nativa local a través de la configuración “Release” permitirá probar la aplicación en un entorno similar al que tendrán los clientes. Es importante probarlo periódicamente a medida que avanza el desarrollo. Al probar la aplicación con la tecnología de runtime y generación de código que tendrán los clientes, se asegura de haber corregido cualquier posible error (por ejemplo, condiciones de carrera potenciales derivadas de distintas características de rendimiento).

Como norma general, pruebe la aplicación de este modo periódicamente a lo largo del desarrollo para asegurarse de identificar y corregir cualquier problema procedente del compilador de .NET Native. En la mayoría de casos, no debería haber ningún error. Sin embargo, aún hay algunas cosas que no funcionan tan bien con .NET Native. Las matrices de más de cuatro dimensiones son un ejemplo. En definitiva, los clientes recibirán la versión compilada de .NET Native de la aplicación, de modo que siempre es buena idea probar la versión a lo largo del desarrollo y antes del envío.

Además de las pruebas con la compilación de .NET Native, puede que observe que la configuración de la compilación de AnyCPU ha desparecido. Con .NET Native, AnyCPU ya no es una configuración de compilación válida porque la compilación nativa depende de la arquitectura. Una consecuencia adicional de esto es que, al empaquetar la aplicación, debe seleccionar las tres configuraciones de arquitectura (x86, x64 y ARM) para asegurarse de que la aplicación se pueda aplicar al máximo de dispositivos. Después de todo, esta es la Plataforma universal de Windows.

Dicho esto, puede crear DLL y bibliotecas de AnyCPU de todos modos y hacer referencia a ellas en su aplicación de UWP. Estos componentes se compilarán en archivos binarios específicos de la arquitectura basados en la configuración del proyecto (.appx) que los usa.

.NET Native en la nube

Una fantástica característica de .NET Native es que el compilador se puede hospedar en la nube. Eso significa que, cuando se aplican al compilador mejoras que pueden tener un impacto positivo sobre la aplicación, el compilador de .NET Native hospedado en la nube de la Tienda puede volver a compilar el paquete de aplicación para obtener los beneficios. Cada vez que se lleve a cabo esta compilación, será transparente para usted como desarrollador, pero, en definitiva, se traducirá en una mayor satisfacción de los consumidores de la aplicación.

Sin embargo, esto puede tener cierto impacto sobre el flujo de trabajo. Por ejemplo, es recomendable asegurarse de disponer siempre de las últimas herramientas instaladas para poder probar la compilación de .NET Native con la versión local del compilador más reciente. Además, al compilar el paquete de la Tienda en Visual Studio, se crean dos paquetes: un .appxupload y un .appx de “prueba” para la instalación de prueba. .appxupload contiene los archivos binarios MSIL, además de una referencia explícita a la versión de la cadena de herramientas de .NET Native que usa la aplicación (a la que se hace referencia en AppxManifest.xml como “ilc.exe”). A continuación, este paquete va a la Tienda y se compila con exactamente la misma versión de la cadena de herramientas de .NET Native. Dado que el compilador está hospedado en la nube, se puede iterar para corregir errores sin necesidad de volver a compilar la aplicación localmente.

Con .NET Native, debe prestar atención al paquete que carga en la Tienda. Dado que la Tienda hace la compilación nativa por usted, no puede cargar los archivos binarios nativos que genera el compilador de .NET Native local. El flujo de trabajo de Visual Studio le guiará a lo largo de este proceso para que seleccione el paquete adecuado. Para obtener instrucciones detalladas para crear un paquete para la Tienda, consulte el artículo de MSDN Library “Empaquetar aplicaciones universales de Windows para Windows 10” en bit.ly/1OQTTG0. Le guiará a lo largo del proceso de creación del paquete para asegurarse de que genera y selecciona el paquete correcto para cargarlo en la tienda.

Depuración con .NET Native

Si detecta problemas en su aplicación y sospecha que se deben a .NET Native, use la técnica que se describe a continuación para depurar el problema. Las configuraciones Release optimizan totalmente el código (por ejemplo, se inserta código en varios puntos) de forma predeterminada, de modo que se pierden algunos artefactos de depuración. Como resultado, intentar depurar una aplicación con una configuración Release puede resultar difícil: puede que experimente un comportamiento de punto de interrupción y un proceso paso a paso imprevisible, además de la incapacidad de inspeccionar variables debido a la optimización de memoria. Dado que el comportamiento predeterminado de las configuraciones Release consiste en usar el compilador de .NET Native con la optimización de código, resulta difícil depurar cualquier problema derivado del proceso de compilación de .NET Native.

Una buena forma de enfocarlo consiste en crear una configuración con una compilación personalizada para el proyecto que use el compilador de .NET Native, pero que no optimice el código por completo. Para crear una configuración con una compilación personalizada, abra Configuration Manager en la lista desplegable de configuración de la compilación, tal y como se muestra en la Ilustración 2.

Acceso a Configuration Manager
Ilustración 2 Acceso a Configuration Manager

En la lista desplegable Configuración de soluciones activas, seleccione <Nueva…> para crear una configuración nueva, tal y como se muestra en la Ilustración 3.

Creación de una configuración nueva
Ilustración 3 Creación de una configuración nueva

Asigne a la configuración nueva un nombre que le resulte útil en otro momento. A mí me gusta usar “Debug .NET Native.” Copie la configuración de la compilación “Release” y, a continuación, haga clic en Aceptar.

Cierre Configuration Manager y abra la página de propiedades del proyecto. Para ello, haga clic con el botón derecho en el proyecto desde el Explorador de soluciones y, a continuación, haga clic en Propiedades. Navegue hasta la pestaña Compilación y compruebe que la opción Compilar con cadena de herramientas de .NET nativa esté marcada y la opción Optimizar código, desmarcada, como se muestra en la Ilustración 4.

Creación de una configuración de compilación para depurar .NET Native
Ilustración 4 Creación de una configuración de compilación para depurar .NET Native

Ahora que ya tiene una configuración de compilación, puede usarla para depurar problemas específicos de .NET Native.

Para obtener más información acerca de la depuración con .NET Native, consulte el artículo de MSDN Library “Debugging .NET Native Windows Universal Apps” (Depuración de aplicaciones universales de Windows de .NET Native), disponible en bit.ly/1Ixd07v.

.NET Native Analyzer

Por supuesto, es recomendable saber depurar problemas, pero, ¿no sería mejor si pudiera evitarlos desde el principio? Microsoft.NETNative.Analyzer (bit.ly/1LugGnO) se puede instalar en la aplicación a través de NuGet. Desde la consola del Administrador de paquetes, puede usar el comando siguiente para instalar el paquete: Install-Package Microsoft.NETNative.Analyzer. En el momento de la implementación, este analizador le proporcionará advertencias si el código no es compatible con el compilador de .NET Native. Hay una pequeña sección de la superficie de .NET que no es compatible, pero, para la mayoría de aplicaciones, esto nunca supondrá ningún problema.

Comentarios finales

Como puede ver, es un momento emocionante para los desarrolladores de Windows .NET. Con UWP, .NET Native y los cambios de NuGet, ahora es más fácil que nunca crear aplicaciones para distintos dispositivos que encantarán a los clientes. Por primera vez, puede aprovechar los últimos avances de cualquier clase de .NET y esperar que la aplicación funcione en cualquier dispositivo con Windows 10.


Daniel Jacobsones administrador de programas para Visual Studio y trabaja en herramientas para desarrolladores de la plataforma Windows. Su dirección de correo electrónico de contacto es dajaco@microsoft.com.

Gracias a los siguientes expertos técnicos por revisar este artículo: 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