Comprendre .NET Standard en 5 min

Les technologies Microsoft adressent un grand nombre de plateformes différentes allant de systèmes d’exploitation comme Windows à des appareils mobiles comme les tablettes. D’autres parts, depuis quelques années, Satya Nadella a impulsé une “ouverture” des technologies Microsoft vers d’autres plateformes que Windows. Cette ouverture a encore augmenté le nombre de plateformes sur lesquelles des technologies pouvaient s’exécuter: Linux, macOS, iOS, Android etc… Toutes ces plateformes ont des spécificités qui nécessitent, pour chacune d’entre elles, une implémentation particulière.

Cet article a pour but d’expliquer l’intérêt du .NET Standard par rapport aux autres approches de Microsoft pour rendre du code Microsoft plus générique et déployable sur plusieurs plateformes.

Explications

Pour Microsoft, un premier challenge a été de tenter d’uniformiser ses bibliothèques pour les rendre moins spécifiques à une plateforme. Ce premier travail a abouti à 3 grandes familles de technologies:

  • .NET Framework déployable sur des machines Windows,
  • .NET Core déployable sur Windows, Linux, macOS mais aussi sur des tablettes, smartphones et Xbox avec Universal Windows Platform (UWP).
  • Xamarin déployable sur Android et iOS.

Cette uniformisation ne permet pas, à elle-seule, d’utiliser un même code pour adresser plusieurs plateformes. Si on développe une bibliothèque, on est obligé d’avoir un projet spécifique pour chaque plateforme et il est nécessaire de compiler tout son code pour chacune des plateformes.

Portable class library (PCL)

Une première tentative pour résoudre ce problème a été d’introduire les Portable Class Library (PCL). Si on sélectionne ce type de projet dans Visual Studio, on peut créer une bibliothèque que l’on pourra déployer sur plusieurs plateformes. En sélectionnant différentes plateform targets (comme .NET Framework, Windows Phone, Xamarin…) dans Visual Studio, on est capable de compiler un même code pour plusieurs plateformes.

Ainsi après compilation, on obtiendra un répertoire différent pour chaque plateforme sélectionnée.

L’approche des PCL rend plus facile le déploiement de bibliothèques sur des plateformes différentes, toutefois elle présente quelques inconvénients:

  • Plus le nombre de plateformes augmente et plus il faudra de compilation spécifiques pour chacune de ces plateformes. Il y aura plus de répertoires de sortie concernant ces plateformes ce qui augmente considérablement la complexité de ce type d’approche car il y a presqu’une dizaine de plateformes possibles (.NET Framework, Silverlight, Windows 8.0, Windows Store Apps, Windows Phone 8.1, Xamarin.iOS, Xamarin.Android).
  • Toutes les API ne sont pas disponibles pour toutes les plateformes. Suivant la plateforme sur laquelle on souhaite déployer, certaines fonctionnalités .NET peuvent ne pas être disponibles.
  • Si on ajoute une nouvelle plateforme, il faudra recompiler la bibliothèque pour qu’elle soit disponible pour cette plateforme.

.NET Standard

Pour éviter de généraliser la démarche des PCL pour un grand nombre de plateformes différentes et pour encapsuler la complexité d’un déploiement sur plusieurs plateformes, la 2e approche de Microsoft a été d’introduire une abstraction supplémentaire avec le .NET Standard.

.NET Standard permet d’introduire une couche supplémentaire entre le code et les plateformes où seront déployées les bibliothèques:

  • Une bibliothèque .NET Standard définit un ensemble d’API qui sont communes à plusieurs plateformes.
  • Une bibliothèque .NET Standard n’est pas liée à une plateforme. Ainsi faire une bibliothèque se baser sur une version de .NET Standard permet d’éviter de la faire se baser sur une plateforme particulière. Il n’y a donc, plus de lien entre la bibliothèque et la plateforme sur laquelle elle sera déployée.
    Cette caractéristique permet d’éviter d’écrire du code pour une plateforme spécifique. Le code de la bibliothèque est écrit pour une version du .NET Standard.
  • La couche supplémentaire qu’est .NET Standard rend plus facile la mise à jour éventuelle d’une plateforme. Au lieu de compiler une bibliothèque pour une version spécifique de la plateforme, on la compile pour une version du .NET Standard.
    Si la nouvelle version d’une plateforme est compatible avec le .NET Standard sur lequel se base des bibliothèques, on peut mettre à jour la plateforme sans craindre une incompatibilité de ces bibliothèques. En effet c’est le .NET Standard qui va garantir la compatibilité.

Différences entre une bibliothèque et un exécutable

L’approche .NET Standard ne convient pas dans tous les cas. Elle permet de faciliter le déploiement de bibliothèques en ajoutant une abstraction de façon à éviter de baser ces bibliothèques directement sur des plateformes. Cette approche est possible car dans la majorité des cas, une bibliothèques de classes n’utilisent pas d’API spécifiques à une plateforme donnée.
En revanche, si une bibliothèque utilise des API trop spécifiques à une plateforme (comme par exemple, WPF qui nécessite un système Windows), elle ne pourra pas se baser sur .NET Standard (.NET Standard ne comporte pas de classes WPF).
De la même façon, un exécutable est spécifique à une plateforme. On ne pourra pas baser une exécutable sur un .NET Standard. Un exécutable est implémenté pour une plateforme précise.

Pour rendre l’approche .NET Standard le plus efficace possible, il faut donc placer un maximum de code dans des bibliothèques qui se basent .NET Standard. Le reste du code, étant plus spécifique, se basera sur une plateforme précise.

Pour résumer, on a donc 2 approches possibles:

  • Une bibliothèque de classes,
  • Un exécutable.

Cas d’une bibliothèque de classes

On peut donc implémenter une bibliothèque de classes .NET Standard en se basant sur:

  • Le .NET Standard qui se base sur des assemblies d’un framework (par exemple .NET Framework ou .NET Core),
  • Eventuellement une autre bibliothèque de classes .NET Standard,
  • Eventuellement un bibliothèque PCL.

Cas d’un exécutable

Un exécutable est plus spécifique à une plateforme, il se base donc directement sur les assemblies d’un framework (par exemple .NET Framework ou .NET Core). Il peut aussi éventuellement avoir des dépendances vers une bibliothèque de classes .NET Standard (qui elle-même se base sur le .NET Standard).

Dépendances à partir de .NET Standard 2.0

A partir de .NET Standard 2.0, une bibliothèque de classes basée sur .NET Standard pourra référencer des assemblies spécifiques au Framework .NET en plus des références vers d’autres bibliothèques .NET Standard. Ces référencements seront possibles à l’aide de compatibility shims. Plus de détails sont disponibles sur .NET Standard Assembly Unification.

Plateforme cible

Comme indiquer plus haut, .NET Standard permet d’indiquer un ensemble d’API utilisables par une bibliothèque de classes. Toutefois, il n’indique pas directement la plateforme cible sur laquelle la bibliothèque sera déployée. Toutes les versions de .NET Standard ne sont pas compatibles avec toutes les plateformes et toutes les versions de plateformes.

Versions de .NET Standard

Ainsi, une plateforme est compatible avec une version précise de .NET Standard. Si on veut déployer une même bibliothèque sur plusieurs plateformes, il faut choisir la version de .NET Standard qui le permet.

On peut résumer la compatibilité entre les frameworks et les versions de .NET Standard dans le tableau suivant:

(1): ces compatiblités sont valables pour le CLI 1.x mais ne seront plus valable quand CLI 2.x sortira.

Quelques remarques sur la version de .NET Standard:

  • Plus la version de .NET Standard augmente et plus des API et des fonctionnalités sont rajoutées au standard. Si une plateforme respecte .NET Standard 1.6 alors elle respecte toutes les versions précédant la 1.6 (de 1.0 à 1.6).
  • Plus la version de .NET est basse et plus de frameworks sont supportés, toutefois plus la version de .NET Standard est basse et moins il y a de fonctionnalités.
Confusions liées à .NET Standard 2.0 (pas de breaking changes)

Il y a eu une grande confusion avec .NET Standard 2.0 car Microsoft a changé son approche entre 2 pre-releases.

1ère approche (valable pour le CLI 1.x):
Dans un premier temps, Microsoft avait envisagé un breaking change entre .NET Standard 1.6 et 2.0. De la version 1.0 à la version 1.6, chaque incrément de version correspond à l’ajout de nouveau élément dans le standard. Pour passer de la version 1.6 à 2.0 de .NET Standard, Microsoft voulait enlever des éléments d’API. La version 2.0 comportait donc moins d’éléments que la version 1.6.

Cette suppression d’éléments entraînait un “breaking change” entre la version 1.6 et 2.0. Ainsi, une bibliothèque de classes compatible avec la version 2.0 n’était pas compatible avec la version 1.6 et 1.5.

De même, le Framework .NET 4.6.1 était compatible de .NET Standard 1.0 à 1.4 puis était compatible avec .NET Standard 2.0. En revanche il n’était pas compatible avec .NET Standard 1.5 et 1.6.
Cette approche est seulement valable pour la version du CLI (Command Line Interface) 1.x.

2e approche (valable pour le CLI 2.x):
Dorénavant, il n’y a pas de breaking changes entre .NET Standard 1.6 et 2.0. La version 2.0 de .NET Standard comporte plus d’éléments d’API que la version 1.6. Ainsi, une bibliothèque de classes compatible avec .NET Standard 2.0 est compatible de 1.0 à 2.0.

De même le Framework .NET 4.6.1 est compatible du .NET Standard 1.0 à 2.0.
A la sortie de la version 2.x du CLI (Command Line Interface), cette approche sera définitive.

Pour avoir la liste plus complête de toutes les plateformes, on peut se reporter à la documentation de .NET Standard sur Github.

Fonctionnalités de .NET Standard

Comme indiqué plus haut, plus la version de .NET Standard augmente et plus le standard comporte de fonctionnalités. Le choix de la version de .NET Standard sur laquelle on va baser une bibliothèque de classes doit se faire suivant les fonctionnalités disponibles.

Les fonctionnalités présentent dans .NET Standard sont les suivantes:

  • .NET Standard 1.0:
    • types primitifs,
    • Reflection dans System.Reflection,
    • Task Parallel Library (TPL) dans System.Threading.Tasks,
    • Collections dans System.Collections,
    • Linq dans System.Linq.
  • .NET Standard 1.1:
    • Les éléments du .NET Standard 1.0,
    • Les structures de collections concurrentes dans System.Collections.Concurrent,
    • Les services d’interopérabilité COM et platform invoke dans System.Runtime.InteropServices,
    • HttpClient dans System.Net.Client.
  • .NET Standard 1.2:
    • Les éléments du .NET Standard 1.1,
    • La classe Timer dans System.Threading,
    • Ajout du paramétrage LargeObjectHeapCompactMode pour le garbage collector dans System.Runtime.
  • .NET Standard 1.3:
    • Les éléments du .NET Standard 1.2,
    • Les classes pour s’interfacer avec le système de fichiers comme File ou Directory dans System.IO,
    • S’interfacer avec une Console avec la classe Console dans System,
    • Gestion des sockets dans System.Net,
    • Ajout d’éléments pour s’interfacer avec l’environnement avec la classe Envionment dans System,
    • Ajout de la classe AsyncLocal dans System.Threading,
    • La classe StringBuilder dans System.Text.
  • .NET Standard 1.4:
    • Les éléments du .NET Standard 1.3,
    • Ajout de l’algorithme ECDSA dans la classe ECDsa dans System.Security.Cryptography.
  • .NET Standard 1.5:
    • Les éléments du .NET Standard 1.4,
    • Des éléments liés à la Reflection dans la classe Activator dans System et ajout de la classe Assembly, Module dans System.Reflection.
    • Ajout de la classe BufferedStream dans System.IO,
  • .NET Standard 1.6:
    • Les éléments du .NET Standard 1.5,
    • Possibilité de compiler les Expression Trees dans la classe Expression dans System.Linq.Expressions.
    • Ajout de la classe ECCurve dans System.Security.Cryptography,
    • Ajout de plus de fonctionnalités dans la classe Regex dans System.Text.RegularExpressions.

Pour .NET Standard 2.0, on peut avoir une vue d’ensemble des fonctionnalités et des API qui sont prises en compte:

On peut avoir une liste exhaustive des fonctionnalités par version de .NET Standard en allant sur: https://github.com/dotnet/standard/tree/master/docs/versions.

.NET API Browser

La disponibilité de classes dépends de la version et de la plateforme cible. Etant donné la multitude de plateformes cible, on peut avoir des difficultés à savoir si une classe existe pour la version de Framework utilisée et pour la plateforme sur laquelle on va déployer.

Pour rendre plus facile cette vérification, Microsoft a mis en place le .NET API Browser.

On peut avoir une documentation précise des classes utilisables en fonction de la plateforme sur le .NET API Browser.

Support des fonctionnalités suivant la plateforme

On peut penser que si une classe est présente pour une version donnée de .NET Standard alors toutes les plateformes supporteront l’implémentation de cette classe. C’est vrai la plupart du temps mais pas dans tous les cas.

Certaines classes sont spécifiques à une plateforme. Ainsi pour assurer une homogénéité des classes définies dans .NET Standard, Microsoft peut les avoir inclus alors qu’elles ne s’appliquent pas pour certaines plateformes. Plusieurs comportements sont possibles, suivants les cas, Microsoft implémente 2 approches:

  • Lancer une exception de type PlatformNotSupportedException à l’exécution quand une classe est incompatible avec la plateforme sur laquelle elle est exécutée.
  • Emuler le comportement de la classe même si la plateforme n’est pas adaptée: par exemple il est possible d’émuler l’accès à la base de registres sur d’autres plateformes que Windows.
Il faut tester les fonctionnalités suivant toutes les plateformes

Certaines classes de .NET Standard peuvent être spécifiques pour certaines plateformes. Ainsi si on base une bibliothèque sur une version de .NET Standard comprenant ces classes, la compilation peut réussir alors qu’à l’exécution, l’utilisation de ces classes peut mener à une exception de type PlatformNotSupportedException. On est donc pas sûr, après compilation, d’avoir un code exécutable sur toutes les plateformes.

Il faut donc appliquer des tests qui s’exécuteront pour les plateformes sur lesquelles ont veut déployer de façon à être sûr de ne pas déclencher des exceptions à l’exécution de certaines fonctionnalités.

Comprendre le fonctionnement de .NET Standard avec NuGet

Comme indiquer plus haut, .NET Standard permet d’encapsuler la complexité de la gestion de plusieurs plateformes en ajoutant une abstraction. Des bibliothèques de classes peuvent se baser sur un .NET Standard et éviter ainsi de se baser sur une plateforme spécifique.

On a l’impression que les mêmes fichiers seront utilisés pour toutes les plateformes. A vrai dire c’est impossible puisque les plateformes sont trop hétérogènes:

  • Pour une bibliothèque de classes .NET Standard lors du développement: les projets de ce type contiennent des références vers une ou plusieurs assemblies contenues dans des packages NuGet de .NET Standard. Ces packages ne contiennent pas vraiment des assemblies avec une implémentation. Ce sont, soit des métapackages (qui référencent d’autres packages NuGet), soit des packages contenant des assemblies avec des fonctions sans implémentation. Le but de ces assemblies est de simplement permettre la compilation. Elles ne seront pas utilisées à l’exécution.
  • Pour un exécutable consommant une bibliothèque de classes .NET Standard: l’exécutable doit s’exécuter sur une plateforme spécifique. Ainsi, lors de l’exécution de tests ou en débug ce sont des assemblies correspondant à la plateforme de développement qui sont utilisées. De même, à l’exécution de l’exécutable sur une plateforme spécifique, ce sont les assemblies contenant du code adaptés à la plateforme qui seront utilisées.

Il y a donc une logique qui associe certaines assemblies suivant la plateforme sur laquelle sera déployée une bibliothèque de classes .NET Standard. Cette logique est gérée par le compilateur et la plateforme de développement quel que soit le framework utilisé au déploiement (Framework .NET, .NET Core etc…).

Le fonctionnement de .NET Standard est différent entre les versions 1.x et 2.x:

  • Pour les versions 1.x: .NET Standard est consommé sous forme d’un “meta-package” NuGet permettant de télécharger d’autres packages plus spécifiques à une plateforme.
  • Pour les versions 2.x: le contenu du package est différent, ils contiennent une assembly monolitique netstandard.dll.

Pour les versions 1.x

Quand on base une bibliothèque de classes sur .NET Standard 1.x, on consomme un package NuGet appelé NETStandard.Library.

Ce package est un “meta-package” qui ne contient aucune assembly. Il permet seulement de télécharger d’autres packages.

Si on regarde le contenu du package .NETStandard.Library 1.6.1:

  1. Télécharger .NETStandard.Library 1.6.1 sur NuGet.org:
  2. Renommer le fichier de .nupkg à .zip
  3. Décompresser le package pour en voir le contenu

En regardant le contenu du répertoire du package, on se rends compte qu’il n’y aucune assembly. Si on lit le fichier NETStandard.Library.nuspec: on trouve des informations relatives au .NET Standard 1.0, .NET Standard 1.1, .NET Standard 1.2 et .NET Standard 1.3:

<?xml version="1.0" encoding="utf-8"?> 
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> 
  <metadata minClientVersion="2.12"> 
    <id>NETStandard.Library</id> 
    <version>1.6.1</version> 
    <title>NETStandard.Library</title> 
    <!-- ... --> 
    <dependencies> 
      <group targetFramework=".NETStandard1.0"> 
        <!-- ... --> 
      </group> 
      <group targetFramework=".NETStandard1.1"> 
        <!-- ... --> 
      </group> 
      <group targetFramework=".NETStandard1.2"> 
        <!-- ... --> 
      </group> 
      <group targetFramework=".NETStandard1.3"> 
        <!-- ... --> 
      </group> 
    </dependencies> 
  </metadata> 
</package>

Il n’y a pas d’informations relatives aux .NET Standard suivants. Ceci doit s’expliquer par le fait que Microsoft a changé d’approche pour le .NET Standard 2.0 et est revenu sur les packages livrés pour .NET Standard 1.5 et .NET Standard 1.6.

Si on regarde le nœud pour le .NET Standard 1.3:

<group targetFramework=".NETStandard1.3"> 
  <dependency id="Microsoft.NETCore.Platforms" version="1.1.0" /> 
  <dependency id="Microsoft.Win32.Primitives" version="4.3.0" /> 
  <dependency id="System.AppContext" version="4.3.0" /> 
  <dependency id="System.Collections" version="4.3.0" /> 
  <dependency id="System.Collections.Concurrent" version="4.3.0" /> 
  <!-- ... --> 
  <dependency id="System.Security.Cryptography.Algorithms" version="4.3.0" /> 
  <dependency id="System.Security.Cryptography.Encoding" version="4.3.0" /> 
  <dependency id="System.Security.Cryptography.Primitives" version="4.3.0" /> 
  <dependency id="System.Security.Cryptography.X509Certificates" version="4.3.0" /> 
</group>
2 types de dépendances existent pour les frameworks:
  • Des dépendances non spécifiques vers des bibliothèques d’un framework: par exemple pour .NET Core, une partie de ces bibliothèques est fournie sous forme d’assemblies à l’installation de .NET Core. Les assemblies fournies de cette façon sont communes à plusieurs plateformes. Par exemple, les assemblies System.Collections et System.Linq ne sont pas spécifiques à une plateforme.
  • Des dépendances vers des bibliothèques d’un framework spécifiques à une plateforme: par exemple, les assemblies System.IO et System.Security.Cryptography.Algotithms sont spécifiques à une plateforme.

    Dans le cas du Framework .NET, toutes les bibliothèques sont dépendantes de la plateforme puisque le Framework .NET n’est déployable que sur un système Windows. D’autre part, toutes les bibliothèques du Framework .NET sont fournies sous la forme d’assemblies qui sont livrées avec le Framework .NET à son installation.

Pour plus de détails sur .NET Core, voir la page suivante: https://docs.microsoft.com/en-us/dotnet/articles/core/.

Dans le fichier NETStandard.Library.nuspec, on considère les dépendances aux packages suivants:

  • Microsoft.NETCore.Platforms,
  • System.Collections et
  • System.Security.Cryptography.Algorithms

Le package NETStandard.Library ne contient pas d’assemblies.

Microsoft.NETCore.Platforms

Si on télécharge le package Microsoft.NETCore.Platforms en version 1.1.0:

En regardant le contenu, on s’aperçoit qu’il n’y a pas non plus d’assemblies. Par contre 3 fichiers sont importants:

  • Microsoft.NETCore.Platforms.nuspec: il contient la description du package.
  • Dans lib\netstandard1.0\_._: ce fichier est vide.
  • Runtime.json: ce fichier contient des Runtime IDentifiers (RID) qui permettent d’identifier les systèmes d’exploitation cibles sur lesquels une application sera exécutée. Par exemple, pour le système Windows 10 en 64 bits, on peut lire la ligne suivante:
            "win10-x64": { 
                "#import": [ "win10", "win81-x64" ]
    

    Ce fichier indique que NuGet peut restaurer les packages ayant besoin de win10 et win81-x64. Par suite, pour win10, NuGet peut restaurer des packages ayant besoin de win81 et ainsi de suite.

    L’arbre de dépendances est:

    • win10-x64 => win10 => win81 => win8 => win7 => win => any => base et
    • Win10-x64 => win81-x64 => win8-x64 => win7-x64 => win-x64 => any => base.

Pour plus de détails sur le RID: Catalogue d’identificateurs de runtime (RID) .NET Core.

Il permet d’indiquer quelles sont les RID à utiliser pour une plateforme cible donnée.

System.Collections

Si on regarde le package System.Collections en version 4.3.0, il contient:

  • System.Collections.nuspec: ce fichier indique d’autres packages suivant la plateforme cible choisie:
    <?xml version="1.0" encoding="utf-8"?> 
    <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> 
      <metadata minClientVersion="2.12"> 
        <id>System.Collections</id> 
        <version>4.3.0</version> 
        <title>System.Collections</title> 
        <! -- ... --> 
        <dependencies> 
          <! -- ... --> 
          <group targetFramework=".NETStandard1.3"> 
            <dependency id="Microsoft.NETCore.Platforms" version="1.1.0" /> 
            <dependency id="Microsoft.NETCore.Targets" version="1.1.0" /> 
            <dependency id="System.Runtime" version="4.3.0" /> 
          </group> 
          <! -- ... --> 
      </metadata> 
    </package>
    
  • Dans le répertoire lib: on trouve d’autres répertoires suivant les plateformes cibles. Mais ces répertoires contiennent des fichiers vides nommés _._.
  • Dans le répertoire ref: se trouve quelques assemblies suivant les plateformes cibles mais l’implémentation de ces assemblies est vide.
    • Si on prends le répertoire ref\netstandard1.0: on trouve l’assembly System.Collections.dll, toutefois en regardant avec DotPeek, il n’y a pas d’implémentation dans le corps des fonctions.
    • Si on prends le répertoire ref\net45: on trouve encore des fichiers vides nommés _._.

Il n’y pas réellement d’assemblies avec une implémentation dans ce package. Ceci s’explique par le fait que System.Collections n’est pas spécifique à une plateforme. L’implémentation correspondant à ce namespace est fournie par les frameworks. Il n’est donc pas nécessaire de fournir une implémentation particulière.

System.Security.Cryptography.Algorithms

Si on regarde le contenu de System.Security.Cryptography.Algorithms en version 4.3.0:

  • System.Security.Cryptography.Algorithms.nuspec contient d’autres indications sur des dépendances de packages NuGet.
  • Les répertoires lib et ref contiennent d’autres répertoires pour les plateformes cibles. Ces répertoires contiennent une assembly quand le framework ne fournit pas lui-même une implémentation.
  • Le répertoire runtime contient des répertoires désignant un contenu spécifique à un runtime indiqué par le package Microsoft.NETCore.Platforms. Par chaque runtime, il y a aussi une assembly spécifique.

Contrairement à System.Collection, le package System.Security.Cryptography.Algorithms contient des assemblies car il fournit une implémentation spécifique pour chaque plateforme.

Si on prends la référence de package Microsoft.NETCore.Targets dans le fichier System.Collections.nuspec en continuant l’exploration.

En résumé pour les versions 1.x

Pour les versions 1.x, le .NET Standard est composé d’une multitude de packages NuGet contenant:

  • D’autres dépendances vers des packages NuGet,
  • Des fichiers vides lorsque l’implémentation n’est pas nécessaire parce-qu’elle est fournie par un framework (Framework .NET, .NET Core etc…).
  • Des assemblies spécifiques à une plateforme lorsque c’est nécessaire.

Ainsi, si une bibliothèque de classes se base sur .NET Standard, elle n’a pas de dépendances directes vers des packages et par suite des assemblies. Ces dépendances sont indiquées par le package .NET Standard suivant la ou les plateformes cibles.

Lien entre .NET Standard et .NET Core

Un des inconvénients de cette approche est le lien entre les packages NuGet de .NET Core et .NET Standard. En fait pour une bibliothèque de classes basée sur .NET Standard, les packages non spécifiques à une plateforme utilisés proviennent de .NET Core. Le package NuGet .NETStandard.Library permet d’implémenter ce lien entre la bibliothèque de classes et les packages .NET Core.

C’est en partie pour casser ce lien qu’une approche différente a été utilisée pour les versions 2.x de .NET Standard.

Pour les versions 2.x

Il n’y a pas encore de version stable de .NET Standard 2.0 et Microsoft a plusieurs fois changé son approche pour les packages correspondant à cette version.

Microsoft a voulu casser la complexité de la gestion de packages pour les versions 1.x en fournissant un package contenant tout le .NET Standard sans avoir une multitude de packages NuGet comme pour les versions 1.x.

L’approche actuelle utilise plusieurs redirections de type (i.e. type forwarding).

Si on regarde le contenu du package NETStandard.Library 2.0:

  • Une assembly nommée netstandard.dll qui contient tous les types du .NET Standard. Ce fichier se trouve dans build\netstandard2.0\ref. L’assembly netstandard.dll ne contient aucune implémentation mais seulement avec des redirections de types vers des assemblies des frameworks.
  • Des assemblies qui se trouvent dans build\netstandard2.0\ref qui correspondent à des types utilisés par le .NET Standard sans implémentation contenant des redirections de type vers l’assembly netstandard.dll.

Les assemblies qui contiennent ces redirections de types sont des assemblies système comme mscorlib.dll, System.Runtime.x et d’autres assemblies non spécifiques à une plateforme.

Par exemple, en regardant le contenu de mscorlib.dll (qui se trouve dans le package) avec DotPeek, on voit qu’elle ne contient que des redirections vers netstandard.dll:

Redirection de type (Type forwarding)

Ce mécanisme fonctionne en utilisant l’attribut TypeForwardedTo (System.Runtime.CompilerServices.TypeForwardedToAttribute) qui permet de mapper un type dans une assembly source vers un type dans une autre assembly.

Ce mécanisme permet de créer des assemblies avec seulement des types contenant des type forward vers d’autres assemblies, rendant ainsi transparent l’utilisation d’un type en fonction d’une plateforme. Ce sont les outils de développement et le compilateur qui prennent en compte les attributs TypeForwardedTo pour savoir quelle assembly utiliser.

Pour comprendre l’utilisation de la redirection de type avec l’assembly netstandard.dll, il y a plusieurs cas de figure:

  • Une bibliothèque de classes utilisant d’autres bibliothèques .NET Standard
  • Un exécutable qui consomme des bibliothèques de classes .NET Standard.

Une bibliothèque de classes .NET Standard

Dans ce cas, la bibliothèque de classes .NET Standard a une dépendance vers:

  • L’assembly netstandard.dll qui se trouve dans le package NuGet NETStandard.Library
  • Eventuellement des assemblies qui ont elles-mêmes des dépendances vers les assemblies d’un framework (Framework .NET, .NET Core, etc…).

Comme indiqué plus haut, le package NETStandard.Library contient des assemblies avec les mêmes types que le framework mais sans implémentation. Ces assemblies font de la redirection de type vers l’assembly netstandard.dll.

Un exécutable consommant une bibliothèque .NET Standard

Dans le cas d’un exécutable qui consomme une bibliothèque .NET Standard, les dépendances sont:

  • Les assemblies du framework puisque comme indiqué plus haut, un exécutable est spécifique à une plateforme et ne peut pas uniquement se baser sur le .NET Standard.
  • La bibliothèque de classes .NET Standard qui elle-même a une dépendance vers l’assembly netstandard.dll (le cas de cette bibliothèque correspond au cas précédent).
  • Eventuellement des assemblies ayant des dépendances vers les assemblies du framework.

Par suite l’assembly netstandard.dll effectue des redirections de types vers les assemblies spécifiques d’un framework.

Compatibilité entre la version 2.x et les versions 1.x

Dans le cas où on se base sur le .NET Standard 2.0 et qu’on utilise des packages.NET Standard 1.x, on peut se retrouver avec plusieurs assemblies avec le même nom: des assemblies provenant du .NET Standard 1.x et les assemblies contenant les type forward. Pour résoudre les éventuels problèmes de conflits, une adaptation a été nécessaire dans MSBuild pour résoudre les conflits.

Il est possible que Microsoft change encore son approche pour la version définitive de .NET Standard 2.0. Pour davantage d’informations sur ce sujet: Packaging for .NET Standard 2.0.

En résumé pour les versions 2.x

Dans la même façon que pour les versions 2.x du .NET Standard, la redirection de type permet d’avoir des bibliothèques de classes qui se basent seulement sur le .NET Standard et n’ont aucune dépendance directe vers des assemblies spécifiques d’un framework.
D’autres part, par rapport aux versions 1.x, le gros avantage de la redirection de type est qu’elle permet d’éviter d’avoir beaucoup de packages NuGet quasiment vides pour la plupart des types non spécifiques à une plateforme. L’utilisation du .NET Standard devient plus directe puisqu’elle se base, en grande partie, sur une seule assembly netstandard.dll.

3 responses... add one

Merci pour ce très bon article qui met en lumière la complexité de cette grosse machine qu’est .Net Standard.

Juste un point de relecture. Il y a 2 fois est différent dans la phrase suivante:
Pour les versions 2.x: le contenu du package est différent est différent, ils contiennent une assembly monolitique netstandard.dll.

Leave a Reply