ChatGPT解决这个技术问题 Extra ChatGPT

为什么我的 .NET Standard NuGet 包会触发如此多的依赖项?

I've been mucking about with a .NET Standard project and NuGet. I've got a working project and have uploaded it to NuGet.org. My project targets .NET Standard 1.3, which should support .NET Framework 4.6 and .NET Core 1.0.

But when I tried to add my project (via NuGet) to a fresh .NET Framework 4.6 project, the dependencies resolved to 47 packages! They're all system libraries and appear to be dependencies of either Microsoft.NETCore.Platforms or NETStandard.Library 1.6.1. (Gist of full PM output.)

My project only imports (using) a handful of libraries, none of which I added manually; i.e. they're all libraries that "came with" .NET Standard. These libraries are:

System System.Text System.Reflection System.Linq System.Collections.Generic;

The thing is, I decided to make my project target .NET Standard because I wanted it to work seamlessly across .NET Framework and .NET Core applications. I thought the whole point of Standard was to set a minimum level of compatibility. By extension, I suppose I had assumed (perhaps erroneously) that libraries like System.Console would be automatically available in either Core or Framework.

I didn't notice anything like this when I tested my Standard project as a dependency in a Framework and Core project within the same solution, so I'm suspicious that this might be a NuGet thing.

What's really going on here? And how can I make my .NET Standard library available on NuGet without a huge list of dependencies?

Is it a problem with the way I've specified my NuGet package? Or have I fundamentally misunderstood something?

Have you noticed whether anything is actually added to the output folder, or whether it's just a scary list of dependencies when installing the package? My experience is that it's the latter, which is still not great, but not as worrying.
@JonSkeet My packages folder now has a long list of folders in it, when I was really only expecting one. Addtionally, the "manage NuGet packages" screen shows all the packages (some of which have pending updates).
Right - but what's in the bin directory? (FWIW, I completely agree that this is a very unfortunate situation. I don't know whether it's been improved for later releases. But this is why for some packages I produce, I create a net45 target as well as a netstandard1.x version...)
Ah, gotcha. My bin directory has 22 DLLs in it, including the one I was expecting.
Eek - so those 22 include System.Text.dll etc? That I wouldn't expect. (That's not to say you've done anything wrong. I'd just expected it to be all okay without those extra DLLs.) Although if there were 47 packages listed, 22 DLLs suggest there are fewer...

M
Martin Ullrich

You haven't done anything wrong, this is expected to happen. If you want nothing more than your own DLL being added to a new .NET Framework project, you have to target .NET Standard 2.0 for your library wait for a .NET Framework version that natively supports both the API and assembly versions - which is going to be 4.7.2 (while .NET Framework 4.7.1 supports all the APIs, there were bugs with how some assemblies are versioned and so the tooling (VS 2017 15.5+) will add additional assemblies to fix that).

What you are seeing are side effects of how .NET Standard is built and the support for the supported frameworks is implemented. This is also different based on the .NET Standard version you target and the tooling used to reference the library package.

In .NET Standard < 2.0, you reference the NETStandard.Library meta-package which in turn references additional (System.*) packages. Those packages contain the reference assemblies that make up the ".NET Standard Contract" - a set of APIs and the assembly names + versions.

When the NuGet package you create for .NET Standard 1.0-1.6 is then referenced by an application, these individual packages don't bring in the reference assemblies but rather implementation assemblies for the framework that the application targets.

For .NET Core, these match the assemblies that are already part of the runtime so the DLL files won't end up next to the built application. This changed however when a new set of packages was released for .NET Core 1.1 (NETStandard.Library version 1.6.1). This resulted in applications built for .NET Core 1.0 ending up getting newer implementation assemblies that were meant to be included in .NET Core 1.1 (luckily, 1.1 was then made the "long-term support" version since that sparked a discussion about which assemblies are part of the LTS promise).

On .NET Framework these libraries (with some exceptions like System.Net.Http) don't do much - they just forward to the system assemblies. So for example the "contract" defines that System.Object is defined in a System.Runtime.dll assembly. So the System.Runtime.dll file you end up with in a .NET Framework application contains a System.Runtime.dll that contains type forward to .NET Framework's mscorlib.dll. .NET Core already contains a different System.Runtime.dll that does something different for that platform. This mechanism allows for a single DLL file to work on both platforms since those type forwards and additional implementations assure the same "contract" (types + assemblies + assembly versions) working on both implementations.

.NET Standard 2.0 aimed to reduce the number of packages and DLLs being necessary and also to remove requiring updates to NETStandard.Library whenever a new .NET Core version is released.

So for .NET Standard 2.0 and .NET Core 2.0, the NETStandard.Library package only brings reference assemblies for compiling code to a project, but the resulting NuGet package no longer depends on this package. So when you create a library targeting .NET Standard 2.0 and publish it, it will have no NuGet dependencies (unless you add additional ones).

The logic of what "support libraries" to bring in when consuming a .NET Standard library was moved to the tooling that is used during build. So when a library that contains a reference to a netstandard.dll is added to a .NET Framework project, the tooling will then add necessary support DLLs based on the version of .NET Framework being used. This was done for .NET Standard 2.0 as well as .NET Standard 1.5+ since .NET Framework 4.6.1 was retroactively made compatible with .NET Standard 2.0 (was 1.4 previously) through these kinds of DLL files. The same tooling also makes sure that even if NuGet packages are somehow brought in to such an application project, any .NET Standard implementation libraries brought in via NuGet are removed from the build. So if you reference a .NET Standard 1.0 NuGet package that was built when .NET Core 1.0 was released, all its NuGet dependencies are trimmed out and you get the support libraries shipped with the build tooling instead.

The idea was that .NET Framework 4.7.1 would contain all the necessary assemblies "inbox" so that a netstandard.dll, System.Runtime.dll etc. are part of .NET Framework and any .NET Standard 1.0-2.0 DLL file would "just work", the problem was that these "inbox" dll files had a too low version number for some assemblies so libraries would fail to load - this was fixed by changing the tooling again to include DLL files with higher version numbers as support libraries which in turn forward to the "inbox" .NET Framework assemblies. This is planned to be fixed in .NET Framework 4.7.2.


My attention has been drawn to a workaround: blog.tdwright.co.uk/2017/11/21/…
Yes, NuGet's get-nearest-framework logic prefers the same target framework with lower versions before considering others (so a net471 project will even choose net35 over netstandard2.0)
Just another DLL hell.
The DLL hell on steroids.
DLL hell indeed. Trying to transition a medium size set of interdependent apps and libraries, even just adding new asp.net core applications (targeting full framework) referencing older shared libraries has been an absolute nightmare of System lib version mis-matches, particularly when IoC containers scan assemblies for types.
K
Kevinoid

I just ran into this problem as well. The blog post you linked in a comment to Martin Ullrich's answer led me to a solution that worked for me: Using NuGet multi-targeting. By changing:

<TargetFramework>netstandard1.0</TargetFramework>

to

<TargetFrameworks>netstandard1.0;netstandard2.0;net45</TargetFrameworks>

in the project .csproj file. This causes the project to be built separately for each target framework and the resulting NuGet package only depends on NETStandard.Library for netstandard1.0. Since NuGet chooses the net45 binaries for any full .NET Framework version, this avoids the unnecessary dependencies when installing the package.


Thanks @Kevinoid. That's the blog post I (OP) wrote summarising Martin's answer above. Nice that it's come full circle.
awesome! Tks ! Save my day;
C
CorrM

You Can Pick .Net 4.7.2, that will solve annoying dependencies

for more info: https://weblog.west-wind.com/posts/2019/Feb/19/Using-NET-Standard-with-Full-Framework-NET


Thanks for your answer. For context, .NET 4.7.2 wasn't available when this question was asked in November 2017 - it was only release in April the following year.
C
CAD bloke

If you're on .NET 4.6 and you're trying to figure out which ones you need to deploy, search your CSPROJ file for \System. (not a regex) - they are the ones in packages that need to be copied with your app, the rest should be framework DLLs.

To test this theory get rid of them in your local build and run that to make sure the deployed version won't break...

In the bin folder, do dir /b System*.dll > textfile.txt to get a list of the DLLs.

inserted "DEL " in front of all the names,

I also found Microsoft.Win32.Primitives.dll & netstandard.dll which weren't needed in 4.6 either.

and save it as a .CMD file - uh, not in the bin folder ok?

add it as a post-build process. $(ProjectDir)DeleteSuperfluousSystemDlls.cmd

I'd love to get off .NET 4.6 but AutoCAD breaks badly when the framework is too modern for it.

edit...

Here is some copy-paste ...

Step 1 - in your CSPROJ file ...

<!--https://stackoverflow.com/questions/2387456/msbuild-exec-task-without-blocking/21181071#21181071-->
  <!--Launch a Process in Parallel-->
  <UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <!--The file path is the full path to the executable file to run-->
      <FilePath ParameterType="System.String" Required="true" />
      <!--The arguments should contain all the command line arguments that need to be sent to the application-->
      <Arguments ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[
  System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments);
  processStartInfo.UseShellExecute = true;
  System.Diagnostics.Process.Start(processStartInfo);
  ]]></Code>
    </Task>
  </UsingTask>
  <Target Name="AfterBuild">
    <ExecAsync FilePath="$(ProjectDir)\Deployment\DeleteSuperfluousSystemDlls.cmd" Arguments="$(TargetDir)" />
  </Target>

Step 2. The batch file ...

Edit the list created by dir /b System*.dll > textfile.txt to look a lot like

del %1Microsoft.Win32.Primitives.dll
del %1netstandard.dll
del %1System.AppContext.dll
del %1System.Collections.Concurrent.dll
del %1System.Collections.dll
del %1System.Collections.NonGeneric.dll
del %1System.Collections.Specialized.dll
del %1System.ComponentModel.dll

but don't forget to remove the ones actually need so they don't get deleted.