程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

C# 13 和 .NET 9 全知道 :7 包装和分发 .NET 类型 (3)

balukai 2025-01-10 11:32:33 文章精选 12 ℃

AOT 本地化要求

不同操作系统有额外的要求:

  • 在 Windows 上,您必须安装包含所有默认组件的 Visual Studio 桌面开发 C++工作负载。
  • 在 Linux 上,您必须安装.NET 运行时依赖的编译器工具链和库的开发包。例如,对于 Ubuntu 18.04 或更高版本: sudo apt-get install clang zlib1g-dev
  • 警告!不支持跨平台原生 AOT 发布。这意味着您必须在您将要部署的操作系统上运行发布操作。例如,您不能在 Linux 上发布原生 AOT 项目以供稍后在 Windows 上运行,反之亦然。

启用项目本机 AOT

要启用项目中本机 AOT 发布,请将 <PublishAot> 元素添加到项目文件中,如下所示,高亮显示的标记:

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <PublishAot>true</PublishAot>

构建本地 AOT 项目

现在,让我们通过使用控制台应用程序的新 AOT 选项来查看一个实际示例:

  1. 在名为 Chapter07 的解决方案中,添加一个符合 AOT 兼容性的本地控制台应用程序项目,如下列表中定义:项目模板:控制台应用程序 / console --aot解决方案文件和文件夹: Chapter07项目文件和文件夹: AotConsole不要使用顶级语句:已清除启用本地 AOT 发布:已选择

如果您代码编辑器尚未提供 AOT 选项,请创建一个传统的控制台应用程序,然后您需要手动启用 AOT,如步骤 2 所示,或使用 dotnet CLI。

  1. 在项目文件中,请注意已启用本地 AOT 发布以及不变全球化,如下所示标记突出显示:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <PublishAot>true</PublishAot>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>
</Project>

显式地将不变全球化设置为 true 是.NET 8 控制台应用程序项目模板中的新功能。它旨在使控制台应用程序不受文化限制,以便可以在世界任何地方部署并具有相同的行为。如果您将此属性设置为 false ,或者如果该元素缺失,则控制台应用程序将默认为托管计算机的当前文化。您可以在以下链接中了解更多关于不变全球化模式的信息:https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md。

  1. 修改项目文件以在所有 C#文件中静态导入 System.Console 类。
  2. Program.cs 中,删除任何现有的语句,然后添加语句以输出当前文化和操作系统版本,如下所示:
using System.Globalization; // To use CultureInfo.
WriteLine("This is an ahead-of-time (AOT) compiled console app.");
WriteLine("Current culture: {0}", CultureInfo.CurrentCulture.DisplayName);
WriteLine("OS version: {0}", Environment.OSVersion);
Write("Press any key to exit.");
ReadKey(intercept: true); // Do not output the key that was pressed.

运行控制台应用程序项目,注意文化属性是不变的,如下所示输出:

This is an ahead-of-time (AOT) compiled console app.
Current culture: Invariant Language (Invariant Country)
OS version: Microsoft Windows NT 10.0.22621.0
  1. 警告!实际上,控制台应用程序尚未进行 AOT 编译;它目前仍然是 JIT 编译,因为我们尚未发布它。

发布本地 AOT 项目

控制台应用程序在开发期间代码未裁剪且即时编译(JIT)正确运行时,一旦使用本地 AOT 发布仍可能失败,因为此时代码被裁剪并即时编译,因此是不同的代码,具有不同的行为。因此,在假设项目可以工作之前,您应该先执行发布操作。

如果您的项目在发布时没有产生任何 AOT 警告,那么您可以确信在发布后 AOT 将正常工作。

让我们发布我们的控制台应用程序:

  1. AotConsole 项目的命令提示符或终端中,使用原生 AOT 发布控制台应用程序,如下所示:
dotnet publish

请注意有关生成本地代码的消息,如下所示输出:

MSBuild version 17.8.0+4ce2ff1f8 for .NET
  Determining projects to restore...
  Restored C:\cs13net9\Chapter07\AotConsole\AotConsole.csproj (in 173 ms).
  AotConsole -> C:\cs13net9\Chapter07\AotConsole\bin\Release\net9.0\win-x64\AotConsole.dll
  Generating native code
  AotConsole -> C:\cs13net9\Chapter07\AotConsole\bin\Release\net9.0\win-x64\publish\
  1. 启动文件资源管理器,打开 bin\Release\net9.0\win-x64\publish 文件夹,注意 AotConsole.exe 文件大约为 1.2MB。 AotConsole.pdb 文件仅用于调试。
  2. 运行 AotConsole.exe 并注意控制台应用程序的行为与之前相同。
  3. Program.cs 中,导入命名空间以处理动态代码组件,如下所示:
using System.Reflection; // To use AssemblyName.
using System.Reflection.Emit; // To use AssemblyBuilder.

Program.cs 中,创建一个动态的汇编构建器,如下面的代码所示:

AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(
  new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);

AotConsole 项目的命令提示符或终端中,使用原生 AOT 发布控制台应用程序,如下所示:

dotnet publish

请注意关于调用 DefineDynamicAssembly 方法的警告,该方法是.NET 团队使用 [RequiresDynamicCode] 属性装饰的,如下所示输出

C:\cs13net9\Chapter07\AotConsole\Program.cs(9,22): warning IL3050: Using member 'System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess)' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. Defining a dynamic assembly requires dynamic code. [C:\cs13net9\Chapter07\AotConsole\AotConsole.csproj]
  1. 取消注释我们无法在 AOT 项目中使用的语句。

更多信息:您可以在以下链接中了解更多关于本地 AOT 的信息:https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/。

打包您的库以供 NuGet 分发

在了解如何创建和打包自己的库之前,我们将回顾一个项目如何使用现有的包。

引用 NuGet 包

假设您想添加由第三方开发者创建的包,例如, Newtonsoft.Json ,这是一个用于处理 JavaScript 对象表示法(JSON)序列化格式的流行包:

  1. AssembliesAndNamespaces 项目中,使用 Visual Studio 的 GUI 或使用 CLI 的 dotnet add package 命令添加对 Newtonsoft.Json NuGet 包的引用。

随着 C# Dev Kit 的 4 月发布,您现在可以使用命令面板中的某些命令直接从 VS Code 管理您的 NuGet 包,具体描述请参阅以下链接:https://devblogs.microsoft.com/nuget/announcing-nuget-commands-in-c-dev-kit/。

  1. 打开 AssembliesAndNamespaces.csproj 文件,注意已添加了一个包引用,如下所示标记:
<ItemGroup>
  <PackageReference Include="Newtonsoft.Json"
                    Version="13.0.3" />
</ItemGroup>

如果您有较新的 Newtonsoft.Json 版本,那么自本章编写以来它已经更新过了。

修复依赖关系

为了持续恢复包并编写可靠的代码,修复依赖关系非常重要。修复依赖关系意味着您正在使用为特定版本的.NET 发布的同一系列包,例如,如以下标记中突出显示的.NET 9 的 SQLite:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Version="9.0.0"
      Include="Microsoft.EntityFrameworkCore.Sqlite" />
  </ItemGroup>
</Project>

为了修复依赖关系,每个包应只有一个版本,且没有额外的限定符。额外的限定符包括测试版( beta1 )、发布候选版( rc4 )和通配符( * )。

通配符允许自动引用和使用未来的版本,因为它们始终代表最新的发布。因此,通配符是危险的,因为它们可能导致使用与未来不兼容的包,从而破坏您的代码。

这可能在编写每月发布新预览版本的书籍时值得冒险,因为你不想像我在 2024 年那样不断更新预览包引用,如下所示标记:

<PackageReference Version="9.0.0-preview.*"
  Include="Microsoft.EntityFrameworkCore.Sqlite" />

为了也能自动使用每年九月和十月到达的候选版本,您可以使模式更加灵活,如下所示标记:

<PackageReference Version="9.0-*"
  Include="Microsoft.EntityFrameworkCore.Sqlite" />

如果您使用 dotnet add package 命令或 Visual Studio 的“管理 NuGet 包”,则默认情况下将使用包的最新特定版本。但如果你从博客文章复制粘贴配置或手动添加引用,可能会包含通配符限定符。

以下依赖项是 NuGet 包引用的示例,它们不是固定的,因此除非您了解其影响,否则应避免使用:

<PackageReference Include="System.Net.Http" Version="4.1.0-*" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta1" />

良好实践:Microsoft 保证如果您将依赖项修复到与特定版本的 .NET 一起提供的版本,例如, 9.0.0 ,那么这些包都将协同工作。几乎总是修复依赖项,尤其是在生产部署中。

打包 NuGet 库

现在,让我们打包您之前创建的 SharedLibrary 项目:

  1. SharedLibrary 项目中,请注意类库针对.NET Standard 2.0,因此默认使用 C# 7.3 编译器。如以下标记所示,明确指定 C# 12 编译器:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12</LangVersion>
  </PropertyGroup>
</Project>
  1. SharedLibrary 项目中,重命名 Class1.cs 文件为 StringExtensions.cs
  2. 修改其内容以提供一些有用的扩展方法来验证各种文本值,使用正则表达式,如下所示代码:
using System.Text.RegularExpressions; // To use Regex.
namespace Packt.Shared;
public static class StringExtensions
{
  public static bool IsValidXmlTag(this string input)
  {
    return Regex.IsMatch(input,
      @"^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)#34;);
  }
  public static bool IsValidPassword(this string input)
  {
    // Minimum of eight valid characters.
    return Regex.IsMatch(input, "^[a-zA-Z0-9_-]{8,}#34;);
  }
  public static bool IsValidHex(this string input)
  {
    // Three or six valid hex number characters.
    return Regex.IsMatch(input,
      "^#?([a-fA-F0-9]{3}|[a-fA-F0-9]{6})#34;);
  }
}

您将在第 8 章“使用常见.NET 类型”中学习如何编写正则表达式。

  1. SharedLibrary.csproj 中,修改其内容,如下所示高亮标记,并注意以下:
  2. PackageId 必须是全球唯一的,因此如果您想将此 NuGet 包发布到 https://www.nuget.org/ 公共源供他人引用和下载,您必须使用不同的值。
  3. PackageLicenseExpression 必须是来自 https://spdx.org/licenses/ 的值,或者您可以指定一个自定义许可。
  4. 警告!如果您依赖 IntelliSense 来编辑文件,则它可能会误导您使用已弃用的标签名称。例如, <PackageIconUrl> 已弃用,推荐使用 <PackageIcon> 。有时,您不能完全信任自动化工具来帮助您正确操作!推荐的标签名称在以下链接中 MSBuild 属性列的表中进行了说明:https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#pack-target。
  5. 所有其他元素都是不言自明的:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12</LangVersion>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <PackageId>Packt.CSdotnet.SharedLibrary</PackageId>
    <PackageVersion>9.0.0.0</PackageVersion>
    <Title>C# 13 and .NET 9 Shared Library</Title>
    <Authors>Mark J Price</Authors>
    <PackageLicenseExpression>
      MS-PL
    </PackageLicenseExpression>
    <PackageProjectUrl>
      https://github.com/markjprice/cs13net9
    </PackageProjectUrl>
    <PackageReadmeFile>readme.md</PackageReadmeFile>
    <PackageIcon>packt-csdotnet-sharedlibrary.png</PackageIcon>
    <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
    <PackageReleaseNotes>
      Example shared library packaged for NuGet.
    </PackageReleaseNotes>
    <Description>
      Three extension methods to validate a string value.
    </Description>
    <Copyright>
      Copyright ? 2016-2023 Packt Publishing Limited
    </Copyright>
    <PackageTags>string extensions packt csharp dotnet</PackageTags>
  </PropertyGroup>
  <ItemGroup>
    <None Include="packt-csdotnet-sharedlibrary.png"
          PackagePath="\" Pack="true" />
    <None Include="readme.md"
          PackagePath="\" Pack="true" />
  </ItemGroup>
</Project>
  1. <None> 表示一个不参与构建过程的文件。 Pack="true" 表示该文件将被包含在指定包路径位置创建的 NuGet 包中。您可以在以下链接中了解更多信息:https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packing-an-icon-image-file。

良好实践:配置属性值为 truefalse 的值不能包含任何空格。

  1. 下载图标文件,并将其保存到以下链接的 SharedLibrary 项目文件夹中:https://github.com/markjprice/cs13net9/blob/main/code/Chapter07/SharedLibrary/packt-csdotnet-sharedlibrary.png。
  2. SharedLibrary 项目文件夹中,创建一个名为 readme.md 的文件,其中包含有关该包的一些基本信息,如下所示:
# README for C# 13 and .NET 9 Shared Library
This is a shared library that readers build in the book,
*C# 13 and .NET 9 - Modern Cross-Platform Development Fundamentals*.
  1. 构建发布组件:在 Visual Studio 中,选择工具栏中的发布,然后导航到构建 | 构建共享库。在 VS Code 的终端中,输入 dotnet build -c Release

如果我们没有在项目文件中将 <GeneratePackageOnBuild> 设置为 true ,那么我们就必须手动使用以下额外步骤创建 NuGet 包:

  • 在 Visual Studio 中,导航到构建 | 打包共享库。
  • 在 VS Code 的终端中,输入 dotnet pack -c Release

发布包到公共 NuGet 源

如果您希望每个人都能下载和使用您的 NuGet 包,那么您必须将其上传到公共 NuGet 源,例如微软的:

  1. 启动您喜欢的浏览器并导航到以下链接:https://www.nuget.org/packages/manage/upload。
  2. 您需要先在 https://www.nuget.org/注册一个 Microsoft 账户,然后登录,如果您想上传一个 NuGet 包供其他开发者作为依赖包引用。
  3. 点击浏览...按钮,选择由生成 NuGet 包创建的 .nupkg 文件。文件夹路径应为 cs13net9\Chapter07\SharedLibrary\bin\Release ,文件名为 Packt.CSdotnet.SharedLibrary.9.0.0.nupkg
  4. 确认您在 SharedLibrary.csproj 文件中输入的信息已正确填写,然后点击提交。
  5. 等待几秒钟,然后您将看到一条成功消息,显示您的包已上传,如图 7.4 所示:

良好实践:如果您遇到错误,请检查项目文件中的错误,或阅读有关 PackageReference 格式的更多信息,请访问 https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets。

  1. 点击“框架”选项卡,注意因为我们针对.NET Standard 2.0 进行开发,所以我们的类库可以被所有.NET 平台使用,如图 7.5 所示:

发布包到私有 NuGet 源

组织可以托管自己的私有 NuGet 源。这对于许多开发团队共享工作来说是一种方便的方式。您可以在以下链接中了解更多信息:https://learn.microsoft.com/en-us/nuget/hosting-packages/overview。

探索使用工具的 NuGet 包

一个名为 NuGet Package Explorer 的便捷工具,用于打开和查看关于 NuGet 包的更多详细信息,由 Uno Platform 创建。它不仅是一个网站,还可以作为跨平台应用程序安装。让我们看看它能做什么:

  1. 启动您喜欢的浏览器并导航到以下链接:https://nuget.info。
  2. 在搜索框中,输入 Packt.CSdotnet.SharedLibrary
  3. 选择由 Mark J Price 发布的软件包 v9.0.0,然后点击打开按钮。
  4. 在“内容”部分,展开 lib 文件夹和 netstandard2.0 文件夹。
  5. 选择 SharedLibrary.dll ,并注意如图 7.6 所示的详细信息:
  1. 如果您将来想在本地上使用此工具,请点击浏览器中的安装按钮。
  2. 关闭浏览器。

并非所有浏览器都支持安装此类网络应用。我建议使用 Chrome 进行测试和开发。

测试您的类库包

您现在将通过在 AssembliesAndNamespaces 项目中引用它来测试您上传的包:

  1. AssembliesAndNamespaces 项目中,添加对您的(或我的)包的引用,如下所示,高亮显示的标记中:
<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  <PackageReference Include="Packt.CSdotnet.SharedLibrary"
                    Version="9.0.0" />
</ItemGroup>
  1. 建立 AssembliesAndNamespaces 项目。
  2. Program.cs 中导入 Packt.Shared 命名空间。
  3. Program.cs 中,提示用户输入一些 string 值,然后使用包中的扩展方法进行验证,如下所示代码:
Write("Enter a color value in hex: ");
string? hex = ReadLine();
WriteLine("Is {0} a valid color value? {1}",
  arg0: hex, arg1: hex.IsValidHex());
Write("Enter a XML element: ");
string? xmlTag = ReadLine();
WriteLine("Is {0} a valid XML element? {1}",
  arg0: xmlTag, arg1: xmlTag.IsValidXmlTag());
Write("Enter a password: ");
string? password = ReadLine();
WriteLine("Is {0} a valid password? {1}",
  arg0: password, arg1: password.IsValidPassword());

运行 AssembliesAndNamespaces 项目,按照提示输入一些值,并查看结果,如下所示输出:

Enter a color value in hex: 00ffc8
Is 00ffc8 a valid color value? True
Enter an XML element: <h1 class="<" />
Is <h1 class="<" /> a valid XML element? False
Enter a password: secretsauce
Is secretsauce a valid password? True

与预览功能一起工作

微软在交付对.NET 多个部分(如运行时、语言编译器和 API 库)产生跨域影响的一些新特性时面临挑战。这是典型的“先有鸡还是先有蛋”问题。你先做什么?

从实际角度来看,这意味着尽管微软可能已经完成了实现该功能所需的大部分工作,但整个项目可能直到他们现在每年一次的.NET 发布周期的非常晚期才准备好,这对于在“野外”进行适当的测试来说太晚了。

从.NET 6 开始,微软将在一般可用(GA)版本中包含预览功能。开发者可以选择加入这些预览功能并向微软提供反馈。在随后的 GA 版本中,这些功能可以为所有人启用。

需要注意的是,这个主题是关于预览功能的。这与.NET 或 Visual Studio 的预览版不同。在开发.NET 和 Visual Studio 的过程中,微软会发布预览版以获取开发者的反馈,然后进行最终的 GA 发布。GA 发布后,该功能对所有人可用。在 GA 之前,获取新功能唯一的方式是安装预览版。预览功能不同,因为它们与 GA 发布一起安装,并且必须选择性地启用。

例如,当微软在 2022 年 2 月发布.NET SDK 6.0.200 时,它包括了 C# 11 编译器作为预览功能。这意味着.NET 6 开发者可以选择将语言版本设置为 preview ,然后开始探索 C# 11 功能,如原始字符串字面量和 required 关键字。

一旦在 2022 年 11 月发布了.NET SDK 7.0.100,任何希望继续使用 C# 11 编译器的.NET 6 开发者,就需要为他们的.NET 6 项目使用.NET 7 SDK,并将目标框架设置为 net6.0 ,同时将 <LangVersion> 设置为 11 。这样,他们就可以使用受支持的.NET 7 SDK 和受支持的 C# 11 编译器来构建.NET 6 项目。

2025 年 11 月,微软可能会发布.NET 10 SDK,并配备 C# 14 编译器。然后您可以安装并使用.NET 10 SDK,以获得 C# 14 中可用的任何新功能的益处,同时仍然针对.NET 9,如下所示,突出显示在以下 Project 文件中:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>14</LangVersion> <!--Requires .NET 10 SDK GA-->
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

良好实践:生产代码不支持预览功能。预览功能可能在最终发布前发生破坏性更改。启用预览功能请自行承担风险。切换到 GA 发布版的未来 SDK,如.NET 11,以使用新编译器功能,同时仍针对较旧但支持时间更长的.NET 版本,如.NET 8 或 10。

需要预览功能

[RequiresPreviewFeatures] 属性用于指示使用并因此需要关于预览功能的警告的程序集、类型或成员。代码分析器可以扫描此属性,并在需要时生成警告。如果你的代码没有使用任何预览功能,你将不会看到任何警告。如果你的代码使用了任何预览功能,那么你将看到警告。你的代码还应使用此属性进行装饰,以警告其他开发者你的代码使用了预览功能。

启用预览功能

Project 文件中,添加一个元素以启用预览功能,并添加一个元素以启用预览语言功能,如下所示,以高亮标记:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project>

方法拦截器

拦截器是一种用对自身的调用替换对可拦截方法的调用的方法。这是一个在源生成器中最常用的高级功能。如果您感兴趣,我可能会在第 9 版中添加关于它们的部分。

更多信息:您可以在以下链接中了解更多关于拦截器的信息:https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#interceptors。

最近发表
标签列表