netstandard–El “estándar” que viene

Cuando .NET salió, las cosas eran muy sencillas: había una sola versión de .NET, el .NET Framework, así que como mucho debíamos saber para que versión de .NET era una determinada librería. “Oh, la librería es solo para .NET 2.0 y yo uso .NET 1.1, que mala suerte”. Al margen de eso no había mucho más, todos teníamos claro que significaba .NET.

Para los desarrolladores de librerías el mundo no era tan sencillo: si uno quería ofrecer una versión de una librería que funcionara tanto en .NET 1.1 como en .NET 2.0, podía optar por lo sencillo que era compilarla solo para .NET 1.1. O podía tener dos versiones de la librería, una para .NET 2.0 (aprovechando todas sus ventajas) y otra para .NET 1.1 y compilar ambas. Tener “dos librerías” es tal y como suena: dos ficheros .csproj, que podían compartir más o menos código (ficheros .cs) en función de las circunstancias. En aquellos tiempos lidiar con varios .csproj, ficheros .cs compartidos entre ellos y por supuesto la compilación condicional era el pan de cada día.

Pero con el tiempo fueron surgiendo “sabores” de .NET. Todos ellos compartían una parte de la API básica de .NET (la BCL – Base Class Library) y diferían en el resto. Empezando por el Compact Framework, siguiendo por Silverlight y luego las distintas reencarnaciones del SDK de Windows Phone hasta llegar a UWP.

Para los desarrolladores de librerías el caos empezaba a ser devastador: tu librería es para .NET4 y también para Silverlight y ya puestos para WP7.5 y WP8. Pues ahí andabas, con cuatro .csprojs. El problema principal era que al final tenías 4 binarios (ensamblados) distintos a pesar de que en algunos casos podían incluso ser compilados a partir de exactamente los mismos ficheros .cs. Tener cuatro ensamblados significaba los 4 .csproj (una limitación de .csproj es que solo permite generar binarios para una plataforma, limitación que desaparece con el nuevo project.json), con sus settings, referencias, etc.

Pero incluso los desarrolladores de aplicaciones se empezaban a ver salpicados por todo este proceso… Que querías compartir código de una librería de negocio en una app .NET y Silverlight? Pues debías tener dos .csproj, uno para que te compilase la librería para .NET y otro para que te la compilara en Silverlight. El problema era ya evidente cuando salieron las “aplicaciones universales”. ¿Os acordáis (seguro que sí) de la plantilla de proyecto de aplicación universal? Aquel pseudo-proyecto “shared” donde meter el código compartido y luego el proyecto por cada plataforma…

Round 1 – Portable Class Libraries

En algún momento alguien en Redmond se dio cuenta que eso no podía aguantar mucho más. La solución: las portable class libraries o PCLs. Las PCLs fueron toda una bendición, en especial para los desarrolladores de librerías: Si antes, para hacer una librería compatible con .NET4, Silverlight y WP necesitaba 3 .csproj, ahora bastaba con uno. Al crear una PCL se definía un perfil contra el que compilar. Los perfiles eran combinaciones válidas de plataformas que compartían una API común. Además VS tenía todas las definiciones de perfiles, por lo que nos impedía que llamáramos a un método que podía estar en .NET pero no en Silverlight.

Con una PCL teníamos un solo proyecto (.csproj) que generaba un solo binario que era compatible con todas las plataformas que conformaran el perfil que se había elegido al crear la librería (por supuesto, VS era lo suficientemente hábil para no preguntarnos el perfil, en su lugar nos preguntaba qué plataformas queríamos soportar y él infería el perfil a partir de nuestra respuesta).

La vida de los desarrolladores de librerías fue mucho más sencilla: con un solo proyecto .csproj podían dar soporte a varias plataformas y solo debían volver a varios .csproj cuando no existía perfil de PCL válido para todas las plataformas que se querían soportar.

Lo mismo para los desarrolladores de aplicaciones: compartir código de negocio entre una aplicación WPF y una aplicación Windows Phone era posible gracias a una PCL. El pseudo-proyecto “shared” de la plantilla de “aplicación universal” ya no era necesario

Esta es la situación hoy en día… y va a cambiar

Pero… ¿cuál es el problema de las PCLs?

Las PCLs simplifican la vida a los desarrolladores, pero vienen con un problema de diseño de base: las definiciones de los perfiles son estáticas e immutables con el tiempo.

Es decir, si un desarrollador crea una librería PCL contra un determinado perfil, pongamos el 111 que contiene .NET Framework 4.5, Windows 8 y Windows Phone 8.1 estas son las plataformas que esta PCL va a soportar. A nivel de desarrollo significa que el desarrollador de la PCL está limitado a las APIs comunas de esas tres plataformas.

Imagina que en un futuro aparece un sabor nuevo de .NET (.NET para XYZ) que es un superconjunto de la api para Windows 8. Es decir, todas las APIs de Windows 8 están soportadas en .NET para XYZ. A priori no debería haber ningún problema en que nuestra PCL funcione en .NET para XYZ (ya que esta contiene todas las APIs de Windows 8). Pero a la práctica no la vamos a poder usar.

Si crearámos un proyecto de tipo .NET para XYZ y intentaramos usar la librería PCL veríamos que no podríamos. Se la intentáramos instalar via nuget, éste nos diría que no hay versión de la PCL compatible con .NET para XYZ. Esto es porque el perfil de la librería PCL se asigna al crear la PCL y el perfil contiene una lista de plataformas soportadas. Estas y solo estas son las que la PCL soportará. El que salga a posteriori otra plataforma que podría ser compatible (a nivel de API ofrecida) es irrelevante.

Esto es lo que pretende solucionar netstandard

Como va a funcionar netstandard

Cuando ahora se crea una PCL se le asignan distintos platforms monikers que representan las plataformas que soporta nuestra PCL. Así el perfil 111 que comentábamos antes tiene los monikers portable-net45, netcore45 y wpa81 que define las tres plataformas soportadas. Generalmente usamos más los monikers que el número de perfil, así generalmente se habla de portable-net45+netcore45+wpa81 en lugar “Perfil 111”.

Con netstandard  lo que aparece es un nuevo moniker que es netstandard. Ahora cuando desarrollemos una librería no vamos a vincularla a un conjunto de plataformas como hasta ahora: vamos a vincularla contra una versión de netstandard. Actualmente hay definidas seis versiones de netstandard. Es importante comprender que netstandard es compatible “hacia atrás”: la versión 1.1 de netstandard incorpora todas las apis definidas en netstandard 1.0. Cada versión de netstandard define un conjunto de APIs que pueden usarse.

La norma para desarrolladores de librerías es ceñirse a la versión de netstandard más baja posible. ¿Y eso por qué?

Pues por qué, las plataformas reales (tales como Xamarin, UWP, .NET Framework, y las que sean) implementarán una versión determinada de netstandard. Así, una plataforma que implemente netstandard 1.2 podrá usar librerías PCL que estén compiladas para la versión 1.2 o anteriores de netstandard. Así, si en un futuro aparece “.NET para XYZ” que implementa netstandard 1.2, cualquier PCL existente que haya sido generada contra netstandard 1.2 o anterior (1.1 o 1.0) se podrá usar en proyectos “.NET para XYZ” a pesar de que dicha plataforma no existía cuando se creó la PCL. Así, solucionamos el problema principal de las PCLs: que la lista de plataformas válidas para la PCL era estática.

La norma para desarrolladores de plataformas es implementar la versión más alta de netstandard posible. Si la plataforma “.NET para XYZ” implementa netstandard 1.4 será mucho mejor que si implementa netstandard 1.1, ya que podrá ejecutar muchas más librerías. Por supuesto, eso implica que la plataforma debe implementar muchas más APIs de .NET. Evidentemente una plataforma puede ofrecer más APIs que las que le obliga la versión de netstandard correspondiente. Y habitualmente será así. P. ej. Xamarin.iOS ofrecerá las APIs para iOS además de las de la versión de netstandard que implemente. Eso implica que un desarrollador para Xamarin.iOS podrá usar todas esas APIs (por supuesto), pero alguien que cree una PCL solo podrá usar las APIs de Xamarin.iOS que existan en la versión de netstandard que Xamarin.iOS soporte. La idea es muy simple: desarrollamos para una plataforma, pero las librerías PCL que consumimos son para netstandard. Y todas las plataformas implementan netstandard en alguna versión.

Definiciones actuales de netstandard

Como he dicho antes hay seis versiones ya definidas de netstandard (1.0 – 1.5). Cada una de ellas define un conjunto de APIs (y recuerda que son acumulativas, es decir netstandard 1.1 soporta todas las APIs de netstandard 1.0 además de las suyas).

David Fowler, ha creado un gist, donde explica con código la relación entre netstandard, las APIs soportadas y las plataformas. No entraré en estos detalles en este post, pero es interesante destacar un par de cosas:

  1. La primera versión de .NET Framework que implementa netstandard es .NET Framework 4.5, que implementa netstandard 1.1. Por supuesto .NET Framework 4.5 es mucho más grande que netstandard 1.1 pero a nivel de PCLs significa que .NET Framework 4.5 podrá ejecutar una PCL compilada contra netstandard 1.1 o 1.0  pero no compilada contra netstandard 1.2. Esto significa que seguimos necesitando multi-targeting si queremos atacar a versiones del .NET Framework anteriores.
  2. La última versión de .NET Framework actual (4.6.2) implementa la versión más alta definida de netstandard que es 1.5. La misma versión que implementan Xamarin.iOS, Xamarin.Android y .NET Core. En cambio UWP implementa netstandard 1.4. Por lo tanto una librería netstandard 1.4 se podrá usar en todas esas plataformas, mientras que una librería netstandard 1.5 no se podrá usar en UWP.

En resúmen, netstandard es el “estándar” que viene para permitir la creación de librerías que sean consumibles en varios sabores de .NET, incluso sabores que se creen a posteriori. Y es que, si algo es seguro, es que ¡iremos viendo cada vez más y más sabores de .NET para distintas plataformas!

Saludos!

3 comentarios en “netstandard–El “estándar” que viene”

  1. Estas son el tipo de cosas que muchas veces causa confusión en el desarrollador. Así que, muy esclarecedor el artículo.
    A fin de cuentas, es un paso más en busca de esa universalidad que nos propone ahora Microsoft, con .NET Core.

  2. Que grande eres Edu…
    Como me he reído con el caption Round 1 – Portable Class Libraries.
    Todavía recuerdo las carcajadas del equipo cuando vimos lo que pesaba la PCL de SQL….

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *