1. .NET Core 应用程序基础
我学习过使用 gcc,C++ 和 VIM 编程. 当我开始使用 C# 和 .NET 的时候, 点击 Visual Studio 中的 运行按钮就是魔法, 也带者失望. 失望 - 不是因为我希望编写 Makefile - 而是因为我不知道 运行都做了什么. 所以, 我开始探索. 在本博文中, 我将展示在 .NET Core 中使用的多数基础工具, 并手工创建 .NET Core 应用程序而不借助于 Visual Studio. 如果你是 .NET Core 的新手, 并且希望揭开内幕, 本文就是为您而来. 如果您已经是一个 .NET Core 的开发者, 并且好奇 *.deps.JSON 或者 *.runtimeconfig.JSON 文件是做什么的, 我也会涵盖这些内容.
我将会终止 Visual Studio 的魔法, 而一直使用命令行工具. 为了你能够进行, 您需要
.NET Core 2.1 SDK( 实际上,.NET Core 3.1 SDK 已经发布, 我想你更应该下载这个最新版). 下面的这些步骤是在 macOS 上完成的, 但是它们也同样在 Linux 和 Windows 上一样工作, 如果您将路径更改为 c:\Program Files\dotnet \ 和 dotnet.exe 的话.
- 2. C# 编译器
- C# 编译器将 *.cs 文件编译为 *.dll 文件, 也被称为程序集文件. 程序集文件具有便携可执行文件格式,.NET Core 可以在 Windows,macOS 和 Linux 上执行它..NET Core App 是一系列 *.dll 文件的集合 (包括少量的配置文件). 它可以通过多种程序设计语言, 例如 VB 或者 F# 等所生成, 但是, C# 是最常用的一种.
- C# 编译器可以直接调用来生成程序集文件. C# 编译器可以在 .NET Core SDK 中发现, 并像下面这样被调用.
- dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll -help
让我们为它提供一个输入内容. 首先, 创建名为 Program.cs 的文件, 并编写如下 C# 代码:
- /* Program.cs */
- class Program
- {
- static void Main(string[] args)
- => System.Console.WriteLine("Hello World!");
- }
然后, 在命令行, 执行如下命令:
- > dotnet \
- /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Console.dll \
- -out:Program.dll \
Program.cs
在 .NET Core 3.1 中, NuGetFallbackFoler 已经从 sdk 文件夹中移除了. 这些程序集已经转移到 C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1 文件夹中.
如果在 Windows 下, 注意空格的处理:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -out:Program.dll Program.cs
参数的含义如下:
- dotnet - C# 编译器本身也是一个 .NET Core 应用程序, 所以, 我们需要通过 dotnet 命令来启动它
- /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll - C# 编译器的路径. 在 Windows 上, 路径就是: C:\Program Files\dotnet\
-reference 参数指向了 System.Runtime.dll 和 System.Console.dll, 这些类似于 C++ 中的头文件, 它们为编译器提供关于 System.Object 和 System.Console 的信息.
-out:Program.dll, 输出文件名..dll 的扩展名是 .NET Core 的约定, 并不是必需的. 如果没有指定, 编译器将生成名为 Program.exe 的文件. 在 Windows 系统上, 这会导致一点误解, 因为你并不能通过双击 Program.exe 来启动它, 所以, 在 .NET Core 中, 我们总是使用 .dll 扩展名.
Reference 引用允许我们使用代码中涉及的在其它 .NET Core 代码中定义的成千上万的类型, 例如 List,Integer 以及 HttpClient 类型等等. 但是, 你不得不告诉编译器到哪里去找到它们. 如果你删除掉 -reference:*** 部分, 编译器将会失败, 并返回如下错误:
- Program.cs(1,11): error CS0518: Predefined type 'System.Object' is not defined or imported
- Program.cs(3,26): error CS0518: Predefined type 'System.String' is not defined or imported
- Program.cs(3,16): error CS0518: Predefined type 'System.Void' is not defined or imported
示例中使用的路径是 /usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App. 这些来自与 Microsoft.NETCore.App 这个 NugGet 包, 后面我们会讨论它.
3. runtimeconfig.JSON
对于 .NET Core 应用程序来说, runtime.config.JSON 文件是必需的. 术语 runtime,shared framework, 或者 platform 经常互换, 但是, 在谈论 .NET Core 的时候, 它们是一回事. 该 JSON 配置文件用于运行时.
如果您拥有了上一步所得到的程序集, 您可以试着在命令行运行它, 通过 dotnet 工具. 没有这个 runtime.config.JSON, 该尝试将会失败:
- dotnet Program.dll
- A fatal error was encountered. The library 'libhostpolicy.dylib' required to execute the application was not found in '/Users/nmcmaster/code/'.
在 Windows 环境的 .NET Core 3.1 环境下, 我得到是:
- dotnet Program.dll
- A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'C:\temp\dotnet\'.
- Failed to run as a self-contained App. If this should be a framework-dependent App, add the C:\temp\dotnet\Program.runtimeconfig.JSON file specifying the appropriate framework.
该段说明的意思是,.NET Core 不能找到用于执行 Program.dll 文件所必需的某些文件. 为了解决这个问题, 创建名为 Program.runtimeconfig.JSON 的文件, 并使用如下内容:
- {
- "runtimeOptions": {
- "framework": {
- "name": "Microsoft.NETCore.App",
- "version": "2.0.0"
- }
- }
- }
注意, 在 .NET Core .3.1 下, 文件内容如下:
- {
- "runtimeOptions": {
- "framework": {
- "name": "Microsoft.NETCore.App",
- "version": "3.1.0"
- }
- }
- }
这些设置指示 dotnet 使用 Microsoft.NETCore.App 3.1.0 共享框架. 该框架也是最常使用的框架, 但是, 还有其它的框架, 例如 Microsoft.AspNetCore.App. 不像 .NET Framework 是装个机器范围生效, 可以有多个 .NET Core 共享框架安装在同一台机器上. dotnet 将读取该 JSON 文件, 并在 /usr/local/share/dotnet/shared/$FrameworkName/$Version / 中查找需要的文件并运行应用程序.
说明: 如果有更高版本的 Microsoft.NeTCore.App 补丁安装, 例如 shared/Microsoft.NETCore.App/2.0.4/,dotnet 将自动使用更高版本.
现在, 执行 dotnet Program.dll.
- >dotnet Program.dll
- Hello world!
4. Package 包
包提供了在不同项目之间, 项目组之间以及组织之间共享代码的方式,.NET 程序集被打包到 *.nupkg 文件中, 这仅仅是一个 ZIP 压缩格式文件, 并含有一个 xml 文件 (.nuspec) , 包含有关于该包的元数据.
最流行的一个 .NET 包称为 JSON.NET, 也被称为 Newtonsoft.JSON. 它提供了解析和序列化 JSON 的 API. 我们可以从 NuGet.org 得到它并提取到磁盘上.
- # Bash
- mkdir -p ./packages/Newtonsoft.JSON/10.0.3/
- curl -L https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 | tar -xf - -C ./packages/Newtonsoft.JSON/10.0.3/
- ?
Windows 环境下
- # Windows (powershell)
- mkdir ./packages/Newtonsoft.JSON/10.0.3/
- Invoke-webRequest https://www.nuget.org/api/v2/package/Newtonsoft.Json/10.0.3 -OutFile Newtonsoft.JSON.10.0.3.zip
- Expand-Archive Newtonsoft.JSON.10.0.3.zip -D ./packages/Newtonsoft.JSON/10.0.3/
为了演示它的使用, 我们将更新前一步的示例代码, 以 JSON 对象格式输出信息.
- class Program
- {
- static void Main(string[] args)
- => System.Console.WriteLine(
- Newtonsoft.JSON.JsonConvert.SerializeObject(new { greeting = "Hello World!" }));
- }
这需要添加更多的编译参数到编译器的参数列表中, 以便使用 Newtonsoft.JSON 的 API.
- > dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Console.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Collections.dll \
- -reference:./packages/Newtonsoft.JSON/10.0.3/lib/netstandard1.3/Newtonsoft.JSON.dll \
- -out:Program.dll \
Program.cs
使用 .NET Core 3.1 的命令如下:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -out:Program.dll Program.cs
注意: 显然我们需要 reference:Newtonsoft.JSON.dll, 但是, 为什么需要 System.Collections.dll? 这是因为我们还使用了匿名类型, new { greeting }. 在背后, C# 编译器在匿名类型上生成了一个 .Equals() 方法, 该方法调用了 System.Collections.Generic.EqualityComparer, 它定义在 System.Collections.dll 中.
编译应当成功, 虽然带有一些警告信息.
Program.cs(4,35): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
在 .NET Core 3.1 下, 实际上, 我得到的输出信息如下:
- Microsoft (R) Visual C# Compiler version 3.4.0-beta4-19562-05 (ff930dec)
- Copyright (C) Microsoft Corporation. All rights reserved.
- ?
- warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
- Program.cs(5,11): warning CS1701: Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Newtonsoft.Json' matches identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy
这意味着在 Newtonsoft.JSON 的作者创建 Newtonsoft.JSON.dll 的时候, 他基于的 System.Runtime.dll 的版本是 4.0.20.0. 但是, 现在提供的 System.Runtime.dll 更新一些, 版本是 4.2.0.0. 如果在版本 4.0.20.0 到 4.2.0.0 之间有变化的化, 会导致你运行的应用程序出现问题, 所以, 编译器发出警告. 幸运的是, 这些变更是后向兼容的, 所以 Newtonsoft.JSON 将工作正常. 我们可以通过添加参数 -nowarn:/CS1701 来抑制这些警告.
- > dotnet /usr/local/share/dotnet/sdk/2.1.3/Roslyn/bincore/csc.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Runtime.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Console.dll \
- -reference:/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.App/2.0.0/ref/netcoreapp2.0/System.Collections.dll \
- -reference:./packages/Newtonsoft.JSON/10.0.3/lib/netstandard1.3/Newtonsoft.JSON.dll \
- -nowarn:CS1701 \
- -out:Program.dll \
Program.cs
对于 Windows 环境下的 .NET Core 3.1, 命令如下:
dotnet 'C:\Program Files\dotnet\sdk\3.1.100\Roslyn\bincore\csc.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Runtime.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Console.dll' -reference:'C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Collections.dll' -reference:'./packages/Newtonsoft.Json/10.0.3/lib/netstandard1.3/Newtonsoft.Json.dll' -nowarn:CS1701 -out:Program.dll Program.cs
注意 CS1701 中的字母是大写.
5. 动态链接
在上一步, 我们编译了一个引用 Newtonsoft.dll,System.Runtime.dll 和其它程序集的简单应用程序. 在添加 Newtonsoft.dll 之前, 我们的应用程序工作良好. 但是, 在更新版本之后, 该程序的运行将会失败.
- > dotnet Program.dll
- Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. The system cannot find the file specified.
.NET 在运行时动态链接程序集. 编译器在程序集 Program.dll 中添加了到 Newtonsoft.JSON.dll 的引用, 但并不会将代码复制进来..NET Core 运行时期待能够在应用程序执行的时候能够加载名为 Newtonsoft.JSON.dll 的程序集. 对于 System.Runtime.dll 和 System.Console.dll, 以及其它引用的 System.* 文件也是同样的.
.NET Core 可以通过配置在一系列位置寻找 Newtonsoft.JSON.dll, 但是, 为了简单起见, 我们可以将它复制到 Program.dll 的同一个文件夹中.
- > cp ./packages/Newtonsoft.JSON/10.0.3/lib/netstandard1.3/Newtonsoft.JSON.dll ./
- > dotnet Program.dll
- {
- "greeting":"Hello World!"
- }
为什么我们不需要将 System.Runtime.dll 和其它文件复制过来呢? 这些文件通过 Microsoft.NETCore.App 共享框架动态链接过来, 如前面所述.
6. deps.JSON
deps.JSON 文件是依赖说明文件. 它可以用来配置来自包的动态链接到程序集. 如前所述,.NET Core 可以配置为从多个位置来动态加载程序集. 这些位置包括:
应用程序所在的目录, 与应用程序入口相同的文件夹, 不需要配置.
包的缓存文件夹 (NuGet 恢复缓存或者 NuGet 回落文件夹)
优化之后的包缓存, 或者运行时包存储.
服务目录 (servicing index), 很少使用, 用于 Windows Update 方式
共享框架 (通过 runtimeconfig.JSON 配置)
综上所述, deps.JSON 定义可以动态链接的依赖列表, 通常, 该文件由机器生成, 对于实际的应用程序, 可能变得很大并且很复杂. 但是, 它是纯文本形式的, 所以我们可以使用编辑器来处理它.
添加名为 Program.deps.JSON 的文件到项目中, 内容如下:
- {
- "runtimeTarget": {
- "name": ".NETCoreApp,Version=v2.0"
- },
- "targets": {
- ".NETCoreApp,Version=v2.0": {
- "Newtonsoft.Json/10.0.3": {
- "runtime": {
- "lib/netstandard1.3/Newtonsoft.Json.dll": {
- "assemblyVersion": "10.0.0.0",
- "fileVersion": "10.0.3.21018"
- }
- }
- }
- }
- },
- "libraries": {
- "Newtonsoft.Json/10.0.3": {
- "type": "package",
- "serviceable": false,
- "sha512": ""
- }
- }
- }
为了展示这是如何工作的, 将现在的与 Program.dll 同一文件夹的 Newtonsoft.JSON.dll 删除. 然后, 执行 dotnet Program.dll.
- > rm Newtonsoft.JSON.dll
- > dotnet Program.dll
- Error:
- An assembly specified in the application dependencies manifest (Program.deps.JSON) was not found:
- package: 'Newtonsoft.Json', version: '10.0.3'
- path: 'lib/netstandard1.3/Newtonsoft.Json.dll'
虽然提供了 Program.deps.JSON 文件,.NET Core 还需要一点关于到哪里定位匹配 deps.JSON 文件中程序集的信息. 该配置可以通过如下三种方式之一实现:
*.runtimeconfig.dev.JSON. 这是配置的典型的最佳方式. 添加文件 Program.runtimeconfig.dev.JSON 文件, 其中设置了包文件夹的位置. 它类似于 Program.runtimeconfig.JSON 文件, 但它是可选的. 典型地其中包含了文件完全路径, 所以不适于在不同机器上发布.
- {
- "runtimeOptions": {
- "additionalProbingPaths": [
- "/Users/nmcmaster/code/packages/"
- ]
- }
- }
命令行. 你可以使用 exec 命令来手工指定 dotnet 命令中程序集的位置. 使用 --additionalprobingppath 参数, 可以指定多个值.
> dotnet exec --additionalprobingpath ./packages/ Program.dll
*.runtimeconfig.JSON. 可以添加一个运行时设置来指定新的探测位置. 它可以使用相对路径.
- {
- "runtimeOptions": {
- "framework": {
- "name": "Microsoft.NETCore.App",
- "version": "2.0.0"
- },
- "additionalProbingPaths": [
- "./packages/"
- ]
- }
- }
注意: 在我的 .NET Core 3.1 环境下, 这个 additionalProbingPaths 没有工作. 这里是 Stack Overflow 上的一个问题: https://stackoverflow.com/questions/56844233/additional-probing-paths-for-net-core-3-migration
7. 总结
对于大多数的开发工作, 并不需要这些基础. 类似 NuGet,MSBuild 和 Visual Studio 自动处理获取应用, C# 文件, 调用编译器, 连结到调试器, 以及其它任务. 但是, 我认为知道背后是如何工作的还是非常用用. 当然了, 你还可以更加深入. 在 *.dll 文件中实际有什么? 什么是 *.pdb 文件? 什么是 crossgen 和 libcoreclr? 我会把这些留在其它部分中.
8. 参考信息
- Specs on runtimeconfig.JSON and deps.JSON: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/runtime-configuration-file.md
- Assembly resolution and dynamic linking: https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/corehost.md
深入 .NET Core 基础 - 1:deps.JSON, runtimeconfig.JSON 以及 dll
来源: http://www.bubuko.com/infodetail-3321525.html