101

Suppose I have a class library which I want to target netstandard1.3, but also use BigInteger. Here's a trivial example - the sole source file is 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);            
    }
}

Back in the world of project.json, I would target netstandard1.3 in the frameworks section, and have an explicit dependency on System.Runtime.Numerics, e.g. version 4.0.1. The nuget package I create will list just that dependency.

In the brave new world of csproj-based dotnet tooling (I'm using v1.0.1 of the command-line tools) there's an implicit metapackage package reference to NETStandard.Library 1.6.1 when targeting netstandard1.3. This means that my project file is really small, because it doesn't need the explicit dependency:

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

... but the nuget package produced has a dependency on NETStandard.Library, which suggests that in order to use my small library, you need everything there.

It turns out I can disable that functionality using DisableImplicitFrameworkReferences, then add in the dependency manually again:

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

Now my NuGet package says exactly what it depends on. Intuitively, this feels like a "leaner" package.

So what's the exact difference for a consumer of my library? If someone tries to use it in a UWP application, does the second, "trimmed" form of dependencies mean that the resulting application will be smaller?

By not documenting DisableImplicitFrameworkReferences clearly (as far as I've seen; I read about it in an issue) and by making the implicit dependency the default when creating a project, Microsoft are encouraging users to just depend on the metapackage - but how can I be sure that doesn't have disadvantages when I'm producing a class library package?

Athari
  • 33,702
  • 16
  • 105
  • 146
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    It should be noted that dependency trimming is [being worked on](https://github.com/dotnet/standard/blob/master/Microsoft.Packaging.Tools/docs/trimming.md). Currently, the size of a `Hello World!` self-contained application is reduced to < 10MB. – ABarney Mar 22 '17 at 19:00
  • @ABarney 10MB is still way too much compared to the < **20KB** size of a classic .NET Framework C# hello-world project. – Dai Nov 19 '18 at 08:48

3 Answers3

76

In the past, we've given developers the recommendation to not reference the meta package (NETStandard.Library) from NuGet packages but instead reference individual packages, like System.Runtime and System.Collections. The rationale was that we thought of the meta package as a shorthand for a bunch of packages that were the actual atomic building blocks of the .NET platform. The assumption was: we might end up creating another .NET platform that only supports some of these atomic blocks but not all of them. Hence, the fewer packages you reference, the more portable you'd be. There were also concerns regarding how our tooling deals with large package graphs.

Moving forward, we'll simplify this:

  1. .NET Standard is an atomic building block. In other words, new platforms aren't allowed to subset .NET Standard -- they have to implement all of it.

  2. We're moving away from using packages to describe our platforms, including .NET Standard.

This means, you'll not have to reference any NuGet packages for .NET Standard anymore. You expressed your dependency with the lib folder, which is exactly how it has worked for all other .NET platforms, in particular .NET Framework.

However, right now our tooling will still burn in the reference to NETStandard.Library. There is no harm in that either, it will just become redundant moving forward.

I'll update the FAQ on the .NET Standard repo to include this question.

Update: This question is now part of the FAQ.

Immo Landwerth
  • 3,239
  • 21
  • 22
  • 9
    Thanks - this makes a lot of sense. I think I was partly confused because in the project.json world I had to add dependencies even when packages were logically part of the framework I was targeting (e.g. System.Runtime.Numerics for netstandard1.3) - so I thought NETStandard.Library was really pulling them in as dependencies. – Jon Skeet Mar 22 '17 at 16:59
  • 2
    Bummer. I liked the idea of referencing packages, because it felt like I could reference only what I wanted instead of the whole "kitchen sink". Maybe I'm not thinking about it correctly? – Nate Barbettini Mar 24 '17 at 17:03
  • 3
    You're not necessarily thinking about it the wrong way, but the question is why you care. It's important to understand the platform isn't infinitely composable. When you run in a shared framework environment (.NET Framework, Mono, .NET Core) all these bits are present anyway. If you build a self-contained app (Xamarin, Mono/.NET Core with linker) we can automatically trim based on your actual usage. So either way, you're not losing anything by not referencing smaller blocks. – Immo Landwerth Mar 25 '17 at 00:29
  • 3
    What about having the compiler enforce rules like preventing a developer from making an http request in a business logic project by not allowing that package - waste of effort? – David Ferretti Mar 25 '17 at 02:03
  • Not necessarily, but how do you enforce it, i.e. what stops a developer from adding the reference? I'd write an analyzer that is injected at build time to enforce layer rules and causes errors on the build machine. – Immo Landwerth Jun 12 '17 at 23:59
19

The team used to recommend figuring out what the slimmest package set was. They no longer do this, and recommend people just bring in NETStandard.Library instead (in the case of an SDK-style project, this will be done automatically for you).

I've never gotten a totally straight forward answer as to why that was, so allow me to make some educated guesses.

The primary reason is likely to be that it allows them to hide the differences in versions of the dependent libraries that you would otherwise be required to track yourself when changing target frameworks. It's also a much more user friendly system with the SDK-based project files, because you frankly don't need any references to get a decent chunk of the platform (just like you used to with the default references in Desktop-land, especially mscorlib).

By pushing the meta-definition of what it means to be a netstandard library, or a netcoreapp application into the appropriate NuGet package, they don't have to build any special knowledge into the definition of those things as Visual Studio (or dotnet new) sees them.

Static analysis could be used during publishing to limit the shipped DLLs, which is something they do today when doing native compilation for UWP (albeit with some caveats). They don't do that today for .NET Core, but I presume it's an optimization they've considered (as well as supporting native code).

There's nothing stopping you from being very selective, if you so choose. I believe you'll find that you're nearly the only one doing it, which also defeats the purpose (since it'll be assumed everybody is bringing in NETStandard.Library or Microsoft.NETCore.App).

Brad Wilson
  • 67,914
  • 9
  • 74
  • 83
  • 6
    I agree it definitely defeats the purpose once there's more than one dependency, if any of them brings in `NETStandard.Library`. It's somewhat self-fulfilling, of course... if *I* depend on `NETStandard.Library` for Noda Time, that means any other library built on top of Noda Time has no reason to trim dependencies, etc. It's tempting to be selective for now (Noda Time is heading towards 2.0) then relax a bit later on once conventions have been established - changing from selective to lib-based would be a non-breaking change, I assume, but the reverse is not true. – Jon Skeet Mar 22 '17 at 11:49
8

You shouldn't need to disable the implicit reference. All platforms that the library will be able to run on will already have the assemblies that the NETStandard.Library dependency would require.

The .NET Standard Library is a specification, a set of reference assemblies that you compile against that provides a set of APIs that are guaranteed to exist on a know set of platforms and versions of platforms, such as .NET Core or the .NET Framework. It is not an implementation of these assemblies, just enough of the API shape to allow the compiler to successfully build your code.

The implementation for these APIs are provided by a target platform, such as .NET Core, Mono or .NET Framework. They ship with the platform, because they are an essential part of the platform. So there is no need to specify a smaller dependency set - everything's already there, you won't change that.

The NETStandard.Library package provides these reference assemblies. One point of confusion is the version number - the package is version 1.6.1, but this does not mean ".NET Standard 1.6". It's just the version of the package.

The version of the .NET Standard you're targeting comes from the target framework you specify in your project.

If you're creating a library and want it to run on .NET Standard 1.3, you'd reference the NETStandard.Library package, currently at version 1.6.1. But more importantly, your project file would target netstandard1.3.

The NETStandard.Library package will give you a different set of reference assemblies depending on your target framework moniker (I'm simplifying for brevity, but think lib\netstandard1.0, lib\netstandard1.1 and dependency groups). So if your project targets netstandard1.3, you'll get the 1.3 reference assemblies. If you target netstandard1.6, you'll get the 1.6 reference assemblies.

If you're creating an application, you can't target the .NET Standard. It doesn't make sense - you can't run on a specification. Instead, you target concrete platforms, such as net452 or netcoreapp1.1. NuGet knows the mapping between these platforms and the netstandard target framework monikers, so knows which lib\netstandardX.X folders are compatible with your target platform. It also knows that the dependencies of NETStandard.Library are satisfied by the target platform, so won't pull in any other assemblies.

Similarly, when creating a standalone .NET Core app, the .NET Standard implementation assemblies are copied with your app. The reference to NETStandard.Library does not bring in any other new apps.

Note that dotnet publish will create a standalone application, but it won't doesn't currently do trimming, and will publish all assemblies. This will be handled automatically by tooling, so again, trimming dependencies in your library won't help here.

The only place I can imagine where it might help to remove the NETStandard.Library reference is if you are targeting a platform that doesn't support the .NET Standard, and you can find a package from the .NET Standard where all of the transitive dependencies can run on your target platform. I suspect there aren't many packages that would fit that bill.

Community
  • 1
  • 1
citizenmatt
  • 18,085
  • 5
  • 55
  • 60
  • If you do `dotnet publish` to a specific runtime, it will bring in all the dependencies, including dotnet.exe (or its Linux/OS X equivalent). That should be a completely stand-alone deployment at that point. Check out the results for a unit test project: https://gist.github.com/bradwilson/6cc5a8fdfa18230aa6c99b851fb85c01 – Brad Wilson Mar 22 '17 at 12:51
  • 4
    I think the last paragraph is precisely the issue... and if it weren't an issue, why would we need different versions of netstandard1.x at all? If every platform has everything in netstandard1.6, why even *have* netstandard1.0 as a concept? That's the confusing part for me. – Jon Skeet Mar 22 '17 at 13:28
  • 1
    And the list of platforms that support the .NET Standard doesn't include ANY of the many Unity platforms. – citizenmatt Mar 22 '17 at 13:44
  • @citizenmatt: Right, but doesn't that mean if you deploy an application using a library that depends on NETStandard.Library on Windows 8.1, you end up bringing along a load of the extra packages that are in netstandard1.3-netstandard1.6? That's precisely what I'm trying to avoid - I don't need everything in NETStandard.Library, so why would I want applications that install on older versions to end up including packages they don't need? It feels like I must be misunderstanding something... – Jon Skeet Mar 22 '17 at 14:13
  • @citizenmatt: Hmm. It's starting to make sense, but can we make it more concrete? My calculator library (as per the question) needs `BigInteger`, but that isn't part of netstandard1.3. What happens - in either packaging scenario - when that's used as part of an application which targets a platform which *only* supports netstandard1.3 and earlier? Presumably that platform doesn't have `System.Runtime.Numerics`, so a dependency has to be included in the application package. If I've depended on `NETStandard.Library`, how is that dependency pulled in but not others? Or would it not work at all? – Jon Skeet Mar 22 '17 at 14:56
  • 1
    (Your patience is much appreciated, btw. I'm really hoping this will all make sense, and that it will be of help to many, many developers...) – Jon Skeet Mar 22 '17 at 14:57
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138741/discussion-between-citizenmatt-and-jon-skeet). – citizenmatt Mar 22 '17 at 14:59
  • (Heads-up to those following along - Matt and I will discuss further, then I suggest we update his answer to make sure everything important is there.) – Jon Skeet Mar 22 '17 at 15:21
  • *Looks like dotnet publish does allow creating a standalone application, although it also looks like it doesn't do trimming, and publishes everything.* Correct. We plan on addressing this with a linker. We don't think the right way is to ask all developers to hand trim their graphs. – Immo Landwerth Mar 22 '17 at 17:00
  • I've updated my answer to cover what was in the comments and the discussion afterwards. I'm deleting my old comments – citizenmatt Mar 22 '17 at 18:13