网站首页 > 文章精选 正文
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 选项来查看一个实际示例:
- 在名为 Chapter07 的解决方案中,添加一个符合 AOT 兼容性的本地控制台应用程序项目,如下列表中定义:项目模板:控制台应用程序 / console --aot解决方案文件和文件夹: Chapter07项目文件和文件夹: AotConsole不要使用顶级语句:已清除启用本地 AOT 发布:已选择
如果您代码编辑器尚未提供 AOT 选项,请创建一个传统的控制台应用程序,然后您需要手动启用 AOT,如步骤 2 所示,或使用 dotnet CLI。
- 在项目文件中,请注意已启用本地 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。
- 修改项目文件以在所有 C#文件中静态导入 System.Console 类。
- 在 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
- 警告!实际上,控制台应用程序尚未进行 AOT 编译;它目前仍然是 JIT 编译,因为我们尚未发布它。
发布本地 AOT 项目
控制台应用程序在开发期间代码未裁剪且即时编译(JIT)正确运行时,一旦使用本地 AOT 发布仍可能失败,因为此时代码被裁剪并即时编译,因此是不同的代码,具有不同的行为。因此,在假设项目可以工作之前,您应该先执行发布操作。
如果您的项目在发布时没有产生任何 AOT 警告,那么您可以确信在发布后 AOT 将正常工作。
让我们发布我们的控制台应用程序:
- 在 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\
- 启动文件资源管理器,打开 bin\Release\net9.0\win-x64\publish 文件夹,注意 AotConsole.exe 文件大约为 1.2MB。 AotConsole.pdb 文件仅用于调试。
- 运行 AotConsole.exe 并注意控制台应用程序的行为与之前相同。
- 在 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]
- 取消注释我们无法在 AOT 项目中使用的语句。
更多信息:您可以在以下链接中了解更多关于本地 AOT 的信息:https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/。
打包您的库以供 NuGet 分发
在了解如何创建和打包自己的库之前,我们将回顾一个项目如何使用现有的包。
引用 NuGet 包
假设您想添加由第三方开发者创建的包,例如, Newtonsoft.Json ,这是一个用于处理 JavaScript 对象表示法(JSON)序列化格式的流行包:
- 在 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/。
- 打开 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 项目:
- 在 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>
- 在 SharedLibrary 项目中,重命名 Class1.cs 文件为 StringExtensions.cs 。
- 修改其内容以提供一些有用的扩展方法来验证各种文本值,使用正则表达式,如下所示代码:
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 类型”中学习如何编写正则表达式。
- 在 SharedLibrary.csproj 中,修改其内容,如下所示高亮标记,并注意以下:
- PackageId 必须是全球唯一的,因此如果您想将此 NuGet 包发布到 https://www.nuget.org/ 公共源供他人引用和下载,您必须使用不同的值。
- PackageLicenseExpression 必须是来自 https://spdx.org/licenses/ 的值,或者您可以指定一个自定义许可。
- 警告!如果您依赖 IntelliSense 来编辑文件,则它可能会误导您使用已弃用的标签名称。例如, <PackageIconUrl> 已弃用,推荐使用 <PackageIcon> 。有时,您不能完全信任自动化工具来帮助您正确操作!推荐的标签名称在以下链接中 MSBuild 属性列的表中进行了说明:https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#pack-target。
- 所有其他元素都是不言自明的:
<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>
- <None> 表示一个不参与构建过程的文件。 Pack="true" 表示该文件将被包含在指定包路径位置创建的 NuGet 包中。您可以在以下链接中了解更多信息:https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packing-an-icon-image-file。
良好实践:配置属性值为 true 或 false 的值不能包含任何空格。
- 下载图标文件,并将其保存到以下链接的 SharedLibrary 项目文件夹中:https://github.com/markjprice/cs13net9/blob/main/code/Chapter07/SharedLibrary/packt-csdotnet-sharedlibrary.png。
- 在 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*.
- 构建发布组件:在 Visual Studio 中,选择工具栏中的发布,然后导航到构建 | 构建共享库。在 VS Code 的终端中,输入 dotnet build -c Release 。
如果我们没有在项目文件中将 <GeneratePackageOnBuild> 设置为 true ,那么我们就必须手动使用以下额外步骤创建 NuGet 包:
- 在 Visual Studio 中,导航到构建 | 打包共享库。
- 在 VS Code 的终端中,输入 dotnet pack -c Release 。
发布包到公共 NuGet 源
如果您希望每个人都能下载和使用您的 NuGet 包,那么您必须将其上传到公共 NuGet 源,例如微软的:
- 启动您喜欢的浏览器并导航到以下链接:https://www.nuget.org/packages/manage/upload。
- 您需要先在 https://www.nuget.org/注册一个 Microsoft 账户,然后登录,如果您想上传一个 NuGet 包供其他开发者作为依赖包引用。
- 点击浏览...按钮,选择由生成 NuGet 包创建的 .nupkg 文件。文件夹路径应为 cs13net9\Chapter07\SharedLibrary\bin\Release ,文件名为 Packt.CSdotnet.SharedLibrary.9.0.0.nupkg 。
- 确认您在 SharedLibrary.csproj 文件中输入的信息已正确填写,然后点击提交。
- 等待几秒钟,然后您将看到一条成功消息,显示您的包已上传,如图 7.4 所示:
良好实践:如果您遇到错误,请检查项目文件中的错误,或阅读有关 PackageReference 格式的更多信息,请访问 https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets。
- 点击“框架”选项卡,注意因为我们针对.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 创建。它不仅是一个网站,还可以作为跨平台应用程序安装。让我们看看它能做什么:
- 启动您喜欢的浏览器并导航到以下链接:https://nuget.info。
- 在搜索框中,输入 Packt.CSdotnet.SharedLibrary 。
- 选择由 Mark J Price 发布的软件包 v9.0.0,然后点击打开按钮。
- 在“内容”部分,展开 lib 文件夹和 netstandard2.0 文件夹。
- 选择 SharedLibrary.dll ,并注意如图 7.6 所示的详细信息:
- 如果您将来想在本地上使用此工具,请点击浏览器中的安装按钮。
- 关闭浏览器。
并非所有浏览器都支持安装此类网络应用。我建议使用 Chrome 进行测试和开发。
测试您的类库包
您现在将通过在 AssembliesAndNamespaces 项目中引用它来测试您上传的包:
- 在 AssembliesAndNamespaces 项目中,添加对您的(或我的)包的引用,如下所示,高亮显示的标记中:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Packt.CSdotnet.SharedLibrary"
Version="9.0.0" />
</ItemGroup>
- 建立 AssembliesAndNamespaces 项目。
- 在 Program.cs 中导入 Packt.Shared 命名空间。
- 在 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。
猜你喜欢
- 2025-01-10 要想做好网店的一件代发,需修改MD5,图片秒变新品,你知道吗?
- 2025-01-10 视频MD5值怎么修改?
- 2025-01-10 夏普MD随身听大全篇目之Sharp MD-SS70
- 2025-01-10 你不可缺少的技能——Markdown编辑
- 2025-01-10 Visual Studio Code 和 Visual Studio 免费 Copilot 计划
- 2025-01-10 Gromacs基本模拟流程
- 2025-01-10 居家办公不用愁,这七款办公软件你值得拥有!
- 2025-01-10 [Eclipse篇]05.从菜鸟开始のSpket插件.md
- 2025-01-10 教大家怎么用GitHub免费搭建自己的博客网站
- 2025-01-10 超实用 MarkDown 网页编辑器StackEdit
- 04-23关于linux coreutils/sort.c源码的延展思考最小堆为什么不用自旋
- 04-23一文精通如何使用二叉树
- 04-23二叉树(Binary Tree)
- 04-23数据结构入门:树(Tree)详细介绍
- 04-23数据结构错题收录(六)
- 04-23Kubernetes原理深度解析:万字图文全总结!
- 04-23一站式速查知识总结,助您轻松驾驭容器编排技术(水平扩展控制)
- 04-23kubectl常用删除命令
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)