Quelles sont les implications applicatives d'une bibliothèque netstandard en fonction d'un métapaquet?

100

Supposons que je dispose d'une bibliothèque de classes que je souhaite cibler netstandard1.3, mais que j'utilise également BigInteger. Voici un exemple trivial - le seul fichier source est Adder.cs:

using System;
using System.Numerics;

namespace Calculator
{
    public class Adder
    {
        public static BigInteger Add(int x, int y)
            => new BigInteger(x) + new BigInteger(y);            
    }
}

De retour dans le monde de project.json, je ciblerais netstandard1.3dans la frameworkssection, et aurais une dépendance explicite sur System.Runtime.Numerics, par exemple la version 4.0.1. Le package nuget que je crée ne listera que cette dépendance.

Dans le nouveau monde de l' outillage de base dotnet csproj (J'utilise v1.0.1 des outils de ligne de commande) , il y a une référence paquet métapaquet implicite à NETStandard.Library 1.6.1lors du ciblage netstandard1.3. Cela signifie que mon fichier projet est vraiment petit, car il n'a pas besoin de la dépendance explicite:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
  </PropertyGroup>
</Project>

... mais le paquet nuget produit a une dépendance sur NETStandard.Library, ce qui suggère que pour utiliser ma petite bibliothèque, vous avez besoin de tout .

Il s'avère que je peux désactiver cette fonctionnalité en utilisant DisableImplicitFrameworkReferences, puis ajouter à nouveau la dépendance manuellement:

<Project Sdk="Microsoft.NET.Sdk">    
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Runtime.Numerics" Version="4.0.1" />
  </ItemGroup>    
</Project>

Maintenant, mon package NuGet dit exactement de quoi il dépend. Intuitivement, cela ressemble à un paquet "allégé".

Alors, quelle est la différence exacte pour un consommateur de ma bibliothèque? Si quelqu'un essaie de l'utiliser dans une application UWP, est-ce que la deuxième forme de dépendances "rognée" signifie que l'application résultante sera plus petite?

En ne documentant pas DisableImplicitFrameworkReferencesclairement (pour autant que je l'ai vu; je l'ai lu dans un numéro ) et en faisant de la dépendance implicite la dépendance par défaut lors de la création d'un projet, Microsoft encourage les utilisateurs à dépendre simplement du métapaquet - mais comment puis-je être sûr que cela n'a pas d'inconvénients lorsque je produis un package de bibliothèque de classes?

Jon Skeet
la source
3
Il convient de noter que la réduction des dépendances est en cours d'élaboration . Actuellement, la taille d'une Hello World!application autonome est réduite à <10 Mo.
ABarney
@ABarney 10 Mo est encore beaucoup trop par rapport à la taille <20 Ko d'un projet hello-world classique .NET Framework C #.
Dai

Réponses:

76

Dans le passé, nous avons recommandé aux développeurs de ne pas référencer le meta package ( NETStandard.Library) des packages NuGet, mais plutôt de référencer des packages individuels, comme System.Runtimeet System.Collections. Le raisonnement était que nous considérions le méta-package comme un raccourci pour un ensemble de packages qui étaient les véritables blocs de construction atomiques de la plate-forme .NET. L'hypothèse était la suivante: nous pourrions finir par créer une autre plate-forme .NET qui ne prend en charge que certains de ces blocs atomiques, mais pas tous. Par conséquent, moins vous référencez de packages, plus vous serez portable. Il y avait également des préoccupations concernant la façon dont nos outils traitent les grands graphiques de paquets.

À l'avenir, nous simplifierons ceci:

  1. .NET Standard est un bloc de construction atomique . En d'autres termes, les nouvelles plates-formes ne sont pas autorisées à sous-ensembles .NET Standard - elles doivent tout mettre en œuvre.

  2. Nous abandonnons l'utilisation de packages pour décrire nos plates - formes , y compris .NET Standard.

Cela signifie que vous n'aurez plus à référencer aucun package NuGet pour .NET Standard. Vous avez exprimé votre dépendance avec le dossier lib, qui est exactement la façon dont il a fonctionné pour toutes les autres plates-formes .NET, en particulier .NET Framework.

Cependant, pour le moment, notre outillage brûlera toujours dans la référence à NETStandard.Library. Il n'y a pas de mal à cela non plus, cela deviendra tout simplement redondant à l'avenir.

Je mettrai à jour la FAQ sur le référentiel .NET Standard pour inclure cette question.

Mise à jour : Cette question fait désormais partie de la FAQ .

Immo Landwerth
la source
9
Merci - cela a beaucoup de sens. Je pense que j'étais en partie confus parce que dans le monde project.json, je devais ajouter des dépendances même lorsque les packages faisaient logiquement partie du framework que je ciblais (par exemple System.Runtime.Numerics pour netstandard1.3) - donc je pensais que NETStandard.Library était les tirant vraiment comme des dépendances.
Jon Skeet
2
Dommage. J'ai aimé l'idée de référencer les packages, car j'avais l'impression que je ne pouvais référencer que ce que je voulais au lieu de tout "l'évier de cuisine". Peut-être que je n'y pense pas correctement?
Nate Barbettini
3
Vous n'y pensez pas nécessairement de la mauvaise façon, mais la question est de savoir pourquoi vous vous en souciez. Il est important de comprendre que la plate-forme n'est pas infiniment composable. Lorsque vous exécutez dans un environnement de framework partagé (.NET Framework, Mono, .NET Core), tous ces bits sont de toute façon présents. Si vous créez une application autonome (Xamarin, Mono / .NET Core avec éditeur de liens), nous pouvons automatiquement découper en fonction de votre utilisation réelle. Dans tous les cas, vous ne perdez rien en ne référençant pas des blocs plus petits.
Immo Landwerth
3
Qu'en est-il du fait que le compilateur applique des règles comme empêcher un développeur de faire une requête http dans un projet de logique métier en n'autorisant pas ce package - gaspillage d'efforts?
David Ferretti
Pas nécessairement, mais comment l'appliquer, c'est-à-dire qu'est-ce qui empêche un développeur d'ajouter la référence? J'écrirais un analyseur qui est injecté au moment de la construction pour appliquer les règles de couche et provoquer des erreurs sur la machine de construction.
Immo Landwerth
19

L'équipe avait l'habitude de recommander de déterminer quel était le paquet le plus mince. Ils ne le font plus et recommandent aux gens de simplement apporter NETStandard.Library à la place (dans le cas d'un projet de type SDK, cela se fera automatiquement pour vous).

Je n'ai jamais obtenu de réponse totalement simple quant à la raison de cette situation, alors permettez-moi de faire des suppositions éclairées.

La raison principale est probablement que cela leur permet de masquer les différences dans les versions des bibliothèques dépendantes que vous seriez autrement obligé de suivre vous-même lors du changement de framework cible. C'est également un système beaucoup plus convivial avec les fichiers de projet basés sur le SDK, car vous n'avez franchement pas besoin de références pour obtenir une bonne partie de la plate-forme (tout comme vous le faisiez avec les références par défaut dans Desktop-land, en particulier mscorlib ).

En poussant la méta-définition de ce que signifie être une netstandardbibliothèque ou une netcoreappapplication dans le package NuGet approprié, ils n'ont pas à intégrer de connaissances spéciales dans la définition de ces éléments tels que Visual Studio (ou dotnet new) les voit.

L'analyse statique pourrait être utilisée lors de la publication pour limiter les DLL livrées, ce qu'ils font aujourd'hui lors de la compilation native pour UWP (avec quelques mises en garde). Ils ne le font pas aujourd'hui pour .NET Core, mais je suppose que c'est une optimisation qu'ils ont envisagée (en plus de prendre en charge le code natif).

Rien ne vous empêche d'être très sélectif, si vous le souhaitez. Je crois que vous constaterez que vous êtes presque le seul à le faire, ce qui va également à l'encontre du but (car on supposera que tout le monde apporte NETStandard.Libraryou Microsoft.NETCore.App).

Brad Wilson
la source
6
Je conviens que cela va définitivement à l'encontre de l'objectif une fois qu'il y a plus d'une dépendance, si l'une d'entre elles apporte NETStandard.Library. C'est un peu auto-réalisateur, bien sûr ... si je dépends NETStandard.Libraryde Noda Time, cela signifie que toute autre bibliothèque construite sur Noda Time n'a aucune raison de réduire les dépendances, etc. Il est tentant d'être sélectif pour le moment (Noda Time est se dirigeant vers 2.0) puis relâchez un peu plus tard une fois que les conventions ont été établies - passer de sélectif à lib-based serait un changement sans rupture, je suppose, mais l'inverse n'est pas vrai.
Jon Skeet
8

Vous ne devriez pas avoir besoin de désactiver la référence implicite. Toutes les plates-formes sur lesquelles la bibliothèque pourra s'exécuter auront déjà les assemblys NETStandard.Libraryrequis par la dépendance.

La bibliothèque standard .NET est une spécification, un ensemble d'assemblys de référence sur lesquels vous compilez et qui fournit un ensemble d'API dont l'existence est garantie sur un ensemble connu de plates-formes et de versions de plates-formes, telles que .NET Core ou .NET Framework . Ce n'est pas une implémentation de ces assemblys, juste assez de la forme de l'API pour permettre au compilateur de générer avec succès votre code.

L'implémentation de ces API est fournie par une plate-forme cible, telle que .NET Core, Mono ou .NET Framework. Ils sont livrés avec la plate-forme, car ils sont une partie essentielle de la plate-forme. Il n'est donc pas nécessaire de spécifier un ensemble de dépendances plus petit - tout est déjà là, vous ne changerez pas cela.

Le NETStandard.Librarypackage fournit ces assemblys de référence. Un point de confusion est le numéro de version - le package est la version 1.6.1, mais cela ne signifie pas ".NET Standard 1.6". C'est juste la version du package.

La version du .NET Standard que vous ciblez provient du framework cible que vous spécifiez dans votre projet.

Si vous créez une bibliothèque et que vous souhaitez qu'elle s'exécute sur .NET Standard 1.3, vous référeriez le NETStandard.Librarypackage, actuellement à la version 1.6.1. Mais plus important encore, votre fichier projet ciblerait netstandard1.3.

Le NETStandard.Librarypackage vous donnera un ensemble différent d'assemblys de référence en fonction de votre surnom de framework cible (je simplifie par souci de brièveté, mais pensez lib\netstandard1.0, lib\netstandard1.1et des groupes de dépendances ). Donc, si votre projet cible netstandard1.3, vous obtiendrez les assemblys de référence 1.3. Si vous ciblez netstandard1.6, vous obtiendrez les assemblys de référence 1.6.

Si vous créez une application, vous ne pouvez pas cibler le standard .NET. Cela n'a pas de sens - vous ne pouvez pas exécuter sur une spécification. Au lieu de cela, vous ciblez des plates-formes concrètes, telles que net452ou netcoreapp1.1. NuGet connaît le mappage entre ces plates-formes et les netstandardmonikers du framework cible, donc sait quels lib\netstandardX.Xdossiers sont compatibles avec votre plate-forme cible. Il sait également que les dépendances de NETStandard.Librarysont satisfaites par la plate-forme cible, donc ne tirera aucun autre assemblage.

De même, lors de la création d'une application .NET Core autonome, les assemblys d'implémentation .NET Standard sont copiés avec votre application. La référence à NETStandard.Libraryn'apporte aucune autre nouvelle application.

Notez que dotnet publishcela créera une application autonome , mais ne fera pas actuellement de découpage et publiera tous les assemblys. Cela sera géré automatiquement par les outils , donc encore une fois, le découpage des dépendances dans votre bibliothèque n'aidera pas ici.

Le seul endroit où je peux imaginer où il pourrait être utile de supprimer la NETStandard.Libraryréférence est si vous ciblez une plate-forme qui ne prend pas en charge le .NET Standard, et vous pouvez trouver un package du .NET Standard où toutes les dépendances transitives peuvent s'exécuter sur votre plateforme cible. Je soupçonne qu'il n'y a pas beaucoup de forfaits qui conviendraient à cette facture.

citizenmatt
la source
Si vous utilisez dotnet publishun runtime spécifique, il introduira toutes les dépendances, y compris dotnet.exe (ou son équivalent Linux / OS X). Cela devrait être un déploiement complètement autonome à ce stade. Consultez les résultats d'un projet de test unitaire: gist.github.com/bradwilson/6cc5a8fdfa18230aa6c99b851fb85c01
Brad Wilson
4
Je pense que le dernier paragraphe est précisément le problème ... et si ce n'était pas un problème, pourquoi aurions-nous besoin de différentes versions de netstandard1.x? Si chaque plate-forme a tout dans netstandard1.6, pourquoi même avoir netstandard1.0 comme concept? C'est la partie déroutante pour moi.
Jon Skeet
1
Et la liste des plates-formes qui prennent en charge la norme .NET n'inclut AUCUNE des nombreuses plates-formes Unity.
citizenmatt
1
(Votre patience est très appréciée, btw. J'espère vraiment que tout cela aura du sens et que cela aidera beaucoup, beaucoup de développeurs ...)
Jon Skeet
1
Continuons cette discussion en chat .
citizenmatt