¿Cuáles son las implicaciones de aplicación de una biblioteca netstandard en función de un metapaquete?

100

Supongamos que tengo una biblioteca de clases que quiero apuntar a netstandard1.3, pero también usar BigInteger. Aquí hay un ejemplo trivial: el único archivo fuente es 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 vuelta en el mundo de project.json, apuntaría netstandard1.3en la frameworkssección y tendría una dependencia explícita System.Runtime.Numerics, por ejemplo, la versión 4.0.1. El paquete nuget que creo enumerará solo esa dependencia.

En el valiente nuevo mundo de las herramientas dotnet basadas en csproj (estoy usando la v1.0.1 de las herramientas de línea de comandos) hay una referencia implícita de paquete de metapaquete a la NETStandard.Library 1.6.1hora de apuntar netstandard1.3. Esto significa que mi archivo de proyecto es realmente pequeño, porque no necesita la dependencia explícita:

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

... pero el paquete nuget producido tiene una dependencia de NETStandard.Library, lo que sugiere que para usar mi pequeña biblioteca, necesita todo lo que está allí.

Resulta que puedo deshabilitar esa funcionalidad usando DisableImplicitFrameworkReferences, luego agregar la dependencia manualmente nuevamente:

<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>

Ahora mi paquete NuGet dice exactamente de qué depende. Intuitivamente, esto se siente como un paquete "más delgado".

Entonces, ¿cuál es la diferencia exacta para un consumidor de mi biblioteca? Si alguien intenta usarlo en una aplicación para UWP, ¿la segunda forma de dependencias "recortada" significa que la aplicación resultante será más pequeña?

Al no documentar DisableImplicitFrameworkReferencesclaramente (por lo que he visto; leí sobre esto en un número ) y al hacer que la dependencia implícita sea la predeterminada al crear un proyecto, Microsoft está animando a los usuarios a depender simplemente del metapaquete, pero ¿cómo puedo hacerlo? ¿Seguro que no tiene desventajas cuando estoy produciendo un paquete de biblioteca de clases?

Jon Skeet
fuente
3
Cabe señalar que se está trabajando en el recorte de dependencias . Actualmente, el tamaño de una Hello World!aplicación autónoma se reduce a <10 MB.
ABarney
@ABarney 10 MB sigue siendo demasiado en comparación con el tamaño < 20 KB de un proyecto clásico de .NET Framework C # hello-world.
Dai

Respuestas:

76

En el pasado, les dimos a los desarrolladores la recomendación de no hacer referencia al NETStandard.Librarymetapaquete ( ) de los paquetes NuGet, sino a los paquetes individuales, como System.Runtimey System.Collections. La razón era que pensamos en el metapaquete como una abreviatura de un montón de paquetes que eran los bloques de construcción atómicos reales de la plataforma .NET. La suposición era: podríamos terminar creando otra plataforma .NET que solo admita algunos de estos bloques atómicos pero no todos. Por lo tanto, cuantos menos paquetes haga referencia, más portátil será. También hubo inquietudes con respecto a cómo nuestras herramientas tratan con gráficos de paquetes grandes.

En el futuro, simplificaremos esto:

  1. .NET Standard es un bloque de construcción atómico . En otras palabras, las nuevas plataformas no pueden crear subconjuntos de .NET Standard, tienen que implementarlo todo.

  2. Nos estamos alejando del uso de paquetes para describir nuestras plataformas , incluido .NET Standard.

Esto significa que ya no tendrá que hacer referencia a ningún paquete NuGet para .NET Standard. Expresó su dependencia con la carpeta lib, que es exactamente cómo ha funcionado para todas las demás plataformas .NET, en particular .NET Framework.

Sin embargo, en este momento nuestras herramientas todavía se quemarán en la referencia a NETStandard.Library. No hay nada de malo en eso, simplemente se volverá redundante en el futuro.

Actualizaré las preguntas frecuentes en el repositorio de .NET Standard para incluir esta pregunta.

Actualización : esta pregunta ahora forma parte de las preguntas frecuentes .

Immo Landwerth
fuente
9
Gracias, esto tiene mucho sentido. Creo que estaba en parte confundido porque en el mundo project.json tuve que agregar dependencias incluso cuando los paquetes eran lógicamente parte del marco al que me dirigía (por ejemplo, System.Runtime.Numerics para netstandard1.3), así que pensé que NETStandard.Library era realmente atrayéndolos como dependencias.
Jon Skeet
2
Gorrón. Me gustó la idea de hacer referencia a los paquetes, porque sentía que solo podía hacer referencia a lo que quería en lugar de todo el "fregadero de la cocina". ¿Quizás no lo estoy pensando correctamente?
Nate Barbettini
3
No necesariamente lo estás pensando de manera incorrecta, pero la pregunta es por qué te importa. Es importante comprender que la plataforma no es infinitamente componible. Cuando se ejecuta en un entorno de marco compartido (.NET Framework, Mono, .NET Core), todos estos bits están presentes de todos modos. Si crea una aplicación autónoma (Xamarin, Mono / .NET Core con vinculador), podemos recortar automáticamente en función de su uso real. De cualquier manera, no perderá nada al no hacer referencia a bloques más pequeños.
Immo Landwerth
3
¿Qué tal si el compilador hace cumplir reglas como evitar que un desarrollador realice una solicitud http en un proyecto de lógica empresarial al no permitir ese paquete, una pérdida de esfuerzo?
David Ferretti
No necesariamente, pero ¿cómo lo aplica, es decir, qué impide que un desarrollador agregue la referencia? Escribiría un analizador que se inyecta en el momento de la compilación para hacer cumplir las reglas de capa y provocar errores en la máquina de compilación.
Immo Landwerth
19

El equipo solía recomendar averiguar cuál era el conjunto de paquetes más delgado. Ya no hacen esto y recomiendan que las personas simplemente traigan NETStandard.Library en su lugar (en el caso de un proyecto de estilo SDK, esto se hará automáticamente por usted).

Nunca he obtenido una respuesta totalmente directa de por qué fue así, así que permítanme hacer algunas conjeturas.

Es probable que la razón principal sea que les permite ocultar las diferencias en las versiones de las bibliotecas dependientes que, de lo contrario, se le solicitaría que realizara un seguimiento al cambiar los marcos de destino. También es un sistema mucho más fácil de usar con los archivos de proyecto basados ​​en SDK, porque francamente no necesita ninguna referencia para obtener una parte decente de la plataforma (como solía hacer con las referencias predeterminadas en Desktop-land, especialmente mscorlib ).

Al introducir la metadefinición de lo que significa ser una netstandardbiblioteca o una netcoreappaplicación en el paquete NuGet apropiado, no tienen que incorporar ningún conocimiento especial en la definición de esas cosas como Visual Studio (o dotnet new) las ve.

El análisis estático podría usarse durante la publicación para limitar las DLL enviadas, que es algo que hacen hoy cuando hacen compilación nativa para UWP (aunque con algunas advertencias). Hoy no hacen eso para .NET Core, pero supongo que es una optimización que han considerado (además de admitir código nativo).

No hay nada que le impida ser muy selectivo, si así lo desea. Creo que encontrará que es casi el único que lo hace, lo que también frustra el propósito (ya que se supondrá que todos están trayendo NETStandard.Libraryo Microsoft.NETCore.App).

Brad Wilson
fuente
6
Estoy de acuerdo en que definitivamente anula el propósito una vez que hay más de una dependencia, si alguna de ellas entra en juego NETStandard.Library. Es algo que se cumple, por supuesto ... si me dependerá NETStandard.Libraryde Noda El tiempo, que los medios cualquier otra biblioteca construida en la cima de Noda El tiempo no tiene razón para dependencias de equipamiento, etc. Es tentador ser selectivo por ahora (Noda El tiempo es dirigiéndose hacia 2.0) y luego relájese un poco más tarde una vez que se hayan establecido las convenciones: cambiar de selectivo a basado en lib sería un cambio inquebrantable, supongo, pero lo contrario no es cierto.
Jon Skeet
8

No debería necesitar deshabilitar la referencia implícita. Todas las plataformas en las que la biblioteca podrá ejecutarse ya tendrán los ensamblados que NETStandard.Libraryrequeriría la dependencia.

La biblioteca estándar de .NET es una especificación, un conjunto de ensamblados de referencia con los que se compila que proporciona un conjunto de API cuya existencia está garantizada en un conjunto conocido de plataformas y versiones de plataformas, como .NET Core o .NET Framework. . No es una implementación de estos ensamblados, solo lo suficiente de la forma API para permitir que el compilador compile correctamente su código.

La implementación de estas API la proporciona una plataforma de destino, como .NET Core, Mono o .NET Framework. Se envían con la plataforma, porque son una parte esencial de la plataforma. Por lo tanto, no es necesario especificar un conjunto de dependencias más pequeño; todo ya está allí, no lo cambiará.

El NETStandard.Librarypaquete proporciona estos ensamblados de referencia. Un punto de confusión es el número de versión: el paquete es la versión 1.6.1, pero esto no significa ".NET Standard 1.6". Es solo la versión del paquete.

La versión de .NET Standard a la que se dirige proviene del marco de destino que especifica en su proyecto.

Si está creando una biblioteca y desea que se ejecute en .NET Standard 1.3, debe hacer referencia al NETStandard.Librarypaquete, actualmente en la versión 1.6.1. Pero lo que es más importante, el archivo de su proyecto debería apuntar netstandard1.3.

El NETStandard.Librarypaquete le dará un conjunto diferente de ensamblados de referencia dependiendo de su apodo de marco de destino (estoy simplificando por brevedad, pero piense lib\netstandard1.0, lib\netstandard1.1y grupos de dependencia ). Entonces, si su proyecto tiene como objetivo netstandard1.3, obtendrá los ensamblados de referencia 1.3. Si apunta netstandard1.6, obtendrá los ensamblados de referencia 1.6.

Si está creando una aplicación, no puede apuntar a .NET Standard. No tiene sentido, no puede ejecutar una especificación. En su lugar, apuntas a plataformas concretas, como net452o netcoreapp1.1. NuGet conoce la asignación entre estas plataformas y los netstandardmonikers del marco de destino, por lo que sabe qué lib\netstandardX.Xcarpetas son compatibles con su plataforma de destino. También sabe que las dependencias de NETStandard.Librarylas satisface la plataforma de destino, por lo que no incorporará ningún otro ensamblado.

De manera similar, al crear una aplicación .NET Core independiente, los ensamblados de implementación de .NET Standard se copian con su aplicación. La referencia a NETStandard.Libraryno incluye ninguna otra aplicación nueva.

Tenga en cuenta que dotnet publishcreará una aplicación independiente , pero no lo hará actualmente, y publicará todos los ensamblados. Esto se manejará automáticamente mediante herramientas , por lo que, nuevamente, recortar las dependencias en su biblioteca no ayudará aquí.

El único lugar que puedo imaginar donde podría ayudar eliminar la NETStandard.Libraryreferencia es si está apuntando a una plataforma que no es compatible con .NET Standard, y puede encontrar un paquete de .NET Standard donde se pueden ejecutar todas las dependencias transitivas en su plataforma de destino. Sospecho que no hay muchos paquetes que se ajusten a ese presupuesto.

ciudadanomatt
fuente
Si lo hace dotnet publishen un tiempo de ejecución específico, traerá todas las dependencias, incluido dotnet.exe (o su equivalente Linux / OS X). Esa debería ser una implementación completamente independiente en ese momento. Consulte los resultados de un proyecto de prueba unitaria: gist.github.com/bradwilson/6cc5a8fdfa18230aa6c99b851fb85c01
Brad Wilson
4
Creo que el último párrafo es precisamente el problema ... y si no fuera un problema, ¿por qué necesitaríamos diferentes versiones de netstandard1.x? Si cada plataforma tiene todo en netstandard1.6, ¿por qué incluso tener netstandard1.0 como concepto? Esa es la parte confusa para mí.
Jon Skeet
1
Y la lista de plataformas que admiten .NET Standard no incluye NINGUNA de las muchas plataformas de Unity.
citizenmatt
1
(Su paciencia es muy apreciada, por cierto. Realmente espero que todo esto tenga sentido y que sea de ayuda para muchos, muchos desarrolladores ...)
Jon Skeet
1
Continuemos esta discusión en el chat .
citizenmatt