为什么我的 .NET Standard NuGet 包会触发如此多的依赖项?
Why does my .NET Standard NuGet package trigger so many dependencies?
我一直在研究 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 和 .NET Core 1.0。
但是当我尝试将我的项目(通过 NuGet)添加到新的 .NET Framework 4.6 项目时,依赖项解析为 47 包!它们都是系统库,似乎是 Microsoft.NETCore.Platforms 或 NETStandard.Library 1.6.1 的依赖项。 (Gist of full PM output.)
我的项目只导入 (using
) 少数几个库,none 我手动添加了这些库;也就是说,它们都是 "came with" .NET Standard 的库。这些库是:
- 系统
- System.Text
- System.Reflection
- System.Linq
- System.Collections.Generic;
事实是,我决定让我的项目以 .NET Standard 为目标,因为我希望它能够跨 .NET Framework 和 .NET Core 应用程序无缝工作。我认为标准的全部意义在于设置最低级别的兼容性。推而广之,我想我曾假设(也许是错误的)像 System.Console 这样的库将在核心或框架中自动可用。
当我在同一解决方案中将我的标准项目作为框架和核心项目中的依赖项进行测试时,我没有注意到类似的事情,所以我怀疑这可能是 NuGet 问题。
这里到底发生了什么?以及如何在没有大量依赖项的情况下使我的 .NET Standard 库在 NuGet 上可用?
我指定 NuGet 包的方式有问题吗?还是我从根本上误解了什么?
你没有做错任何事,这在意料之中。如果您只想将自己的 DLL 添加到新的 .NET Framework 项目中,则必须将 .NET Standard 2.0 作为库的目标,等待原生支持 API 和程序集版本的 .NET Framework 版本- 这将是 4.7.2(虽然 .NET Framework 4.7.1 支持所有 APIs,但一些程序集的版本控制存在错误,因此工具(VS 2017 15.5+)将添加额外的程序集解决这个问题)。
您看到的是 .NET Standard 的构建方式和对受支持框架的支持的实现方式的副作用。这也根据您的目标 .NET Standard 版本和用于引用库包的工具而有所不同。
在 .NET Standard < 2.0 中,您引用 NETStandard.Library
元包,后者又引用其他 (System.*
) 包。这些包包含构成“.NET 标准合同”的参考程序集 - 一组 API 和程序集名称 + 版本。
当应用程序引用您为 .NET Standard 1.0-1.6 创建的 NuGet 包时,这些单独的包不会引入引用程序集,而是引入应用程序目标框架的实现程序集。
对于 .NET Core,它们与已经成为运行时一部分的程序集相匹配,因此 DLL 文件不会在构建的应用程序旁边结束。然而,当为 .NET Core 1.1(NETStandard.Library
版本 1.6.1)发布了一组新的包时,这种情况发生了变化。这导致为 .NET Core 1.0 构建的应用程序最终获得更新的实现程序集,这些程序集本应包含在 .NET Core 1.1 中(幸运的是,1.1 随后成为 "long-term support" 版本,因为这引发了关于哪些程序集是LTS 承诺的一部分)。
在 .NET Framework 上,这些库(有一些例外,如 System.Net.Http
)做的不多——它们只是转发给系统程序集。因此,例如 "contract" 定义 System.Object
是在 System.Runtime.dll
程序集中定义的。因此,您最终在 .NET Framework 应用程序中得到的 System.Runtime.dll
文件包含一个 System.Runtime.dll
,其中包含指向 .NET Framework 的 mscorlib.dll
的类型。 .NET Core 已经包含一个不同的 System.Runtime.dll
,它为该平台做一些不同的事情。这种机制允许单个 DLL 文件在两个平台上工作,因为这些类型转发和其他实现确保相同的 "contract"(类型 + 程序集 + 程序集版本)在两个实现上工作。
.NET Standard 2.0 旨在减少必要的包和 DLL 的数量,并在发布新的 .NET Core 版本时删除对 NETStandard.Library
的更新。
所以对于.NET Standard 2.0和.NET Core 2.0,NETStandard.Library
包只为项目带来了编译代码的引用程序集,但是生成的NuGet包不再依赖于这个包。因此,当您创建一个面向 .NET Standard 2.0 的库并发布它时,它将没有 NuGet 依赖项(除非您添加额外的依赖项)。
使用 .NET Standard 库时 "support libraries" 引入的逻辑已移至构建期间使用的工具。因此,当将包含对 netstandard.dll
的引用的库添加到 .NET Framework 项目时,该工具将根据所使用的 .NET Framework 版本添加必要的支持 DLL。这是为 .NET Standard 2.0 和 .NET Standard 1.5+ 完成的,因为 .NET Framework 4.6.1 通过这些类型的 DLL 文件追溯地与 .NET Standard 2.0(之前是 1.4)兼容。相同的工具还确保即使 NuGet 包以某种方式引入此类应用程序项目,任何通过 NuGet 引入的 .NET Standard 实现库都会从构建中删除。因此,如果您引用在 .NET Core 1.0 发布时构建的 .NET Standard 1.0 NuGet 包,它的所有 NuGet 依赖项都将被删除,您将获得构建工具附带的支持库。
当时的想法是,.NET Framework 4.7.1 将包含所有必要的程序集 "inbox",因此 netstandard.dll
、System.Runtime.dll
等都是 .NET Framework 的一部分,任何.NET Standard 1.0-2.0 DLL 文件会 "just work",问题是这些 "inbox" dll 文件对于某些程序集的版本号太低,因此库将无法加载 - 这已通过更改工具解决再次包含具有更高版本号的 DLL 文件作为支持库,这些库又转发给 "inbox" .NET Framework 程序集。这计划在 .NET Framework 4.7.2 中修复。
我刚刚 运行 遇到了这个问题。 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。通过更改:
<TargetFramework>netstandard1.0</TargetFramework>
到
<TargetFrameworks>netstandard1.0;netstandard2.0;net45</TargetFrameworks>
在项目 .csproj
文件中。这会导致为每个目标框架单独构建项目,并且生成的 NuGet 包仅依赖于 NETStandard.Library
for netstandard1.0
。由于 NuGet chooses the net45
binaries for any full .NET Framework version,这避免了安装包时不必要的依赖。
如果您使用的是 .NET 4.6 并且您正在尝试确定需要部署哪些,请在您的 CSPROJ 文件中搜索 \System.
(不是正则表达式)- 它们是包中的那些需要与您的应用程序一起复制,其余的应该是框架 DLL。
为了测试这个理论,在你的本地构建中删除它们,运行确保部署的版本不会崩溃...
- 在 bin 文件夹中,执行
dir /b System*.dll > textfile.txt
以获取 DLL 列表。
- 在所有名字前插入"DEL ",
- 我还发现
Microsoft.Win32.Primitives.dll
& netstandard.dll
4.6 也不需要。
- 并将其另存为 .CMD 文件 - 呃,不在 bin 文件夹中好吗?
- 将其添加为 post-build 进程。
$(ProjectDir)DeleteSuperfluousSystemDlls.cmd
我很想离开 .NET 4.6,但是当框架对它来说太现代时,AutoCAD 会严重崩溃。
编辑...
这是一些 copy-paste ...
第 1 步 - 在您的 CSPROJ 文件中...
<!--
<!--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>
第 2 步。批处理文件 ...
编辑 dir /b System*.dll > textfile.txt
创建的列表,使其看起来很像
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
但不要忘记删除那些真正需要的,这样它们就不会被删除。
您可以选择 .Net 4.7.2,这将解决烦人的依赖关系
更多信息:https://weblog.west-wind.com/posts/2019/Feb/19/Using-NET-Standard-with-Full-Framework-NET
我一直在研究 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 和 .NET Core 1.0。
但是当我尝试将我的项目(通过 NuGet)添加到新的 .NET Framework 4.6 项目时,依赖项解析为 47 包!它们都是系统库,似乎是 Microsoft.NETCore.Platforms 或 NETStandard.Library 1.6.1 的依赖项。 (Gist of full PM output.)
我的项目只导入 (using
) 少数几个库,none 我手动添加了这些库;也就是说,它们都是 "came with" .NET Standard 的库。这些库是:
- 系统
- System.Text
- System.Reflection
- System.Linq
- System.Collections.Generic;
事实是,我决定让我的项目以 .NET Standard 为目标,因为我希望它能够跨 .NET Framework 和 .NET Core 应用程序无缝工作。我认为标准的全部意义在于设置最低级别的兼容性。推而广之,我想我曾假设(也许是错误的)像 System.Console 这样的库将在核心或框架中自动可用。
当我在同一解决方案中将我的标准项目作为框架和核心项目中的依赖项进行测试时,我没有注意到类似的事情,所以我怀疑这可能是 NuGet 问题。
这里到底发生了什么?以及如何在没有大量依赖项的情况下使我的 .NET Standard 库在 NuGet 上可用?
我指定 NuGet 包的方式有问题吗?还是我从根本上误解了什么?
你没有做错任何事,这在意料之中。如果您只想将自己的 DLL 添加到新的 .NET Framework 项目中,则必须将 .NET Standard 2.0 作为库的目标,等待原生支持 API 和程序集版本的 .NET Framework 版本- 这将是 4.7.2(虽然 .NET Framework 4.7.1 支持所有 APIs,但一些程序集的版本控制存在错误,因此工具(VS 2017 15.5+)将添加额外的程序集解决这个问题)。
您看到的是 .NET Standard 的构建方式和对受支持框架的支持的实现方式的副作用。这也根据您的目标 .NET Standard 版本和用于引用库包的工具而有所不同。
在 .NET Standard < 2.0 中,您引用 NETStandard.Library
元包,后者又引用其他 (System.*
) 包。这些包包含构成“.NET 标准合同”的参考程序集 - 一组 API 和程序集名称 + 版本。
当应用程序引用您为 .NET Standard 1.0-1.6 创建的 NuGet 包时,这些单独的包不会引入引用程序集,而是引入应用程序目标框架的实现程序集。
对于 .NET Core,它们与已经成为运行时一部分的程序集相匹配,因此 DLL 文件不会在构建的应用程序旁边结束。然而,当为 .NET Core 1.1(NETStandard.Library
版本 1.6.1)发布了一组新的包时,这种情况发生了变化。这导致为 .NET Core 1.0 构建的应用程序最终获得更新的实现程序集,这些程序集本应包含在 .NET Core 1.1 中(幸运的是,1.1 随后成为 "long-term support" 版本,因为这引发了关于哪些程序集是LTS 承诺的一部分)。
在 .NET Framework 上,这些库(有一些例外,如 System.Net.Http
)做的不多——它们只是转发给系统程序集。因此,例如 "contract" 定义 System.Object
是在 System.Runtime.dll
程序集中定义的。因此,您最终在 .NET Framework 应用程序中得到的 System.Runtime.dll
文件包含一个 System.Runtime.dll
,其中包含指向 .NET Framework 的 mscorlib.dll
的类型。 .NET Core 已经包含一个不同的 System.Runtime.dll
,它为该平台做一些不同的事情。这种机制允许单个 DLL 文件在两个平台上工作,因为这些类型转发和其他实现确保相同的 "contract"(类型 + 程序集 + 程序集版本)在两个实现上工作。
.NET Standard 2.0 旨在减少必要的包和 DLL 的数量,并在发布新的 .NET Core 版本时删除对 NETStandard.Library
的更新。
所以对于.NET Standard 2.0和.NET Core 2.0,NETStandard.Library
包只为项目带来了编译代码的引用程序集,但是生成的NuGet包不再依赖于这个包。因此,当您创建一个面向 .NET Standard 2.0 的库并发布它时,它将没有 NuGet 依赖项(除非您添加额外的依赖项)。
使用 .NET Standard 库时 "support libraries" 引入的逻辑已移至构建期间使用的工具。因此,当将包含对 netstandard.dll
的引用的库添加到 .NET Framework 项目时,该工具将根据所使用的 .NET Framework 版本添加必要的支持 DLL。这是为 .NET Standard 2.0 和 .NET Standard 1.5+ 完成的,因为 .NET Framework 4.6.1 通过这些类型的 DLL 文件追溯地与 .NET Standard 2.0(之前是 1.4)兼容。相同的工具还确保即使 NuGet 包以某种方式引入此类应用程序项目,任何通过 NuGet 引入的 .NET Standard 实现库都会从构建中删除。因此,如果您引用在 .NET Core 1.0 发布时构建的 .NET Standard 1.0 NuGet 包,它的所有 NuGet 依赖项都将被删除,您将获得构建工具附带的支持库。
当时的想法是,.NET Framework 4.7.1 将包含所有必要的程序集 "inbox",因此 netstandard.dll
、System.Runtime.dll
等都是 .NET Framework 的一部分,任何.NET Standard 1.0-2.0 DLL 文件会 "just work",问题是这些 "inbox" dll 文件对于某些程序集的版本号太低,因此库将无法加载 - 这已通过更改工具解决再次包含具有更高版本号的 DLL 文件作为支持库,这些库又转发给 "inbox" .NET Framework 程序集。这计划在 .NET Framework 4.7.2 中修复。
我刚刚 运行 遇到了这个问题。 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。通过更改:
<TargetFramework>netstandard1.0</TargetFramework>
到
<TargetFrameworks>netstandard1.0;netstandard2.0;net45</TargetFrameworks>
在项目 .csproj
文件中。这会导致为每个目标框架单独构建项目,并且生成的 NuGet 包仅依赖于 NETStandard.Library
for netstandard1.0
。由于 NuGet chooses the net45
binaries for any full .NET Framework version,这避免了安装包时不必要的依赖。
如果您使用的是 .NET 4.6 并且您正在尝试确定需要部署哪些,请在您的 CSPROJ 文件中搜索 \System.
(不是正则表达式)- 它们是包中的那些需要与您的应用程序一起复制,其余的应该是框架 DLL。
为了测试这个理论,在你的本地构建中删除它们,运行确保部署的版本不会崩溃...
- 在 bin 文件夹中,执行
dir /b System*.dll > textfile.txt
以获取 DLL 列表。 - 在所有名字前插入"DEL ",
- 我还发现
Microsoft.Win32.Primitives.dll
&netstandard.dll
4.6 也不需要。 - 并将其另存为 .CMD 文件 - 呃,不在 bin 文件夹中好吗?
- 将其添加为 post-build 进程。
$(ProjectDir)DeleteSuperfluousSystemDlls.cmd
我很想离开 .NET 4.6,但是当框架对它来说太现代时,AutoCAD 会严重崩溃。
编辑...
这是一些 copy-paste ...
第 1 步 - 在您的 CSPROJ 文件中...
<!--
<!--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>
第 2 步。批处理文件 ...
编辑 dir /b System*.dll > textfile.txt
创建的列表,使其看起来很像
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
但不要忘记删除那些真正需要的,这样它们就不会被删除。
您可以选择 .Net 4.7.2,这将解决烦人的依赖关系
更多信息:https://weblog.west-wind.com/posts/2019/Feb/19/Using-NET-Standard-with-Full-Framework-NET