.NET runtime 跨平台编码指南:二进制兼容性、Partial 类拆分与平台文件命名规范
.NET runtime 跨平台编码指南二进制兼容性、Partial 类拆分与平台文件命名规范【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime本文基于 .NET runtime 仓库的跨平台编码规范docs/coding-guidelines/cross-platform-guidelines.md展开系统讲解运行时程序集如何做到跨平台二进制兼容、平台特定 API 何时应废弃或抛出PlatformNotSupportedException、如何用 partial class 分层实现平台差异、平台专用源文件应如何命名以及为什么构建系统倾向于“按平台选择源文件”而非#if条件编译。读完本文读者可以掌握在 dotnet/runtime 中编写和维护跨平台代码的完整决策框架并看到这些规范在 System.Private.CoreLib.csproj 中的真实落地方式。核心原则程序集应当跨平台二进制兼容规范的首要问题是程序集是否应当在各平台间保持二进制兼容例如 Windows、Linux、macOS 上使用完全相同的 System.IO.dll答案是肯定的且规范给出了量化预期大约 70% 的 dotnet/runtime 程序集不含任何平台特定代码这些程序集应当跨平台二进制兼容部分场景下托管二进制在所有平台共用但附带按平台各编译一次的本地库native library 逐平台编译托管层不动只有几十个场景下托管代码本身因目标平台Windows、Linux 等不同而实现不同此时同一程序集的二进制无法跨平台通用——该使用哪个二进制由负责分发这些库的 NuGet 包来决定即按 RID 放置不同子目录的产物。这一分层思想决定了日常工作模式绝大多数 BCL 代码应保持平台无关只有当无法抽象时才允许“同一程序集、不同二进制”并交给打包层处理分发差异。平台特定 API 的取舍移除、保留还是抛异常第二个问题一个已存在的平台特定 .NET API何时应该被废弃或移除以支持新方案规范给出的结论是“视情况而定”但给出了两类清晰的边界整个契约本身就是平台特定的应直接不提供。例如Microsoft.Win32.Registry.dll——注册表在 Unix 上毫无意义因此该契约在其他平台上根本不存在而不是“存在但不可用”契约本身跨平台可用但个别成员平台特定的应抛出PlatformNotSupportedException。规范给出的例子是 Unix 上的Console.get_ForegroundColorConsole类本身在 Unix 上可用而设置前景色这一成员在该平台上无对应能力调用时抛PlatformNotSupportedException。同时规范强调了一条总原则只要某个平台上的契约是存在的即 contract 可用就应该努力让 API 在该平台上真正可用而不是让“存在但失败”成为常态。换言之PlatformNotSupportedException是兜底手段而非设计目标。Partial Class托管代码分平台实现的标准手段第三个问题何时应当使用 partial class 来叠加平台特定功能规范明确指出当托管代码需要按底层平台分叉时partial class 是当前采用的标准做法。仓库中大量文件印证了这一约定。例如 NativeAOT 目录下的成对文件Environment.NativeAot.Windows.csEnvironment.NativeAot.Unix.csPInvokeMarshal.Windows.csPInvokeMarshal.Unix.csThread.NativeAot.Windows.cs / Thread.NativeAot.Unix.cs同一类型在.Windows.cs与.Unix.cs两个 partial 文件中各自持有平台实现主文件保持共享逻辑。规范同时承认存在少数走了其他路线的场景但这些场景也可能回归 partial class 方案——也就是说 partial class 是方向性的默认选择。命名规范整型平台专用用前缀Partial 分平台用后缀第四个问题平台特定文件应当如何命名例如FileStream.Windows.cs还是WindowsFileStream.cs规范区分了两种命名模式场景命名模式示例整个类型专属于某个平台使用前缀PlatformPlatformFileStream.cs文件包含某平台的 partial class使用后缀*.平台.csFileStream.Windows.cs、FileStream.Unix.cs这一约定不仅靠人工遵守还有分析器工具兜底。仓库中的 PlatformDocAnalyzer 是一个 Roslyn 分析器专门约束平台特定库的文档与文件放置约定其诊断规则包括PLATDOC001公开类型缺少主源文件{类型名}.cs——即 partial 拆分必须存在一个“主文件”PLATDOC002partial 源文件命名不符合{类型名}.*.cs约定——这正是上述“后缀”规范的机器化检查直接对应规范中“FileStream.Windows.cs”这类命名要求PLATDOC003XML 文档写在了非主文件的 partial 文件中应移动到{类型名}.csPLATDOC004某平台构建生成的文档与平台无关的“规范构建”文档不一致。该分析器只在目标 TFM 是平台特定、且项目启用了UseCompilerGeneratedDocXmlFiletrue时生效见 PlatformDocAnalyzer.OnCompilationStart设计意图是保证各平台用户看到的 API 文档完全一致——因为平台分叉实现隐藏在 partial 文件里文档若分散在各平台文件中就会出现“同一 API 在不同平台文档不一致”的问题。分析器的测试可参考 eng/analyzers/README.md 与 PlatformDocAnalyzerTests.cs。另外从源码结构看仓库还存在第三种变体*PlatformNotSupported.cs见下文构建配置中出现的InMemoryAssemblyLoader.PlatformNotSupported.cs用于“该能力在部分平台不支持”的场景与规范中PlatformNotSupportedException的语义一脉相承。构建组织优先按平台选择源文件而不是 #define第五个问题何时应使用 define 语句何时应在构建环境中包含不同的源文件规范的立场非常明确尽可能避免#if条件编译defines优先做法是在构建中只包含当前平台相关的源文件。这一原则在仓库构建系统中被贯彻得相当彻底。以 System.Private.CoreLib.csproj 为例可以看到三层实践定义常量被严格收敛。项目显式重置了所有默认常量并声明“SPCL 对哪些常量被定义很敏感而 SDK 默认会加一些”见 System.Private.CoreLib.csproj!-- Ignore all previous constants since SPCL is sensitive to what is defined and the Sdk adds some by default -- DefineConstantsCORECLR;NETCOREAPP;SYSTEM_PRIVATE_CORELIB/DefineConstants DisableImplicitConfigurationDefinestrue/DisableImplicitConfigurationDefines架构维度的常量也按Platform属性精确追加TARGET_AMD64、TARGET_X86、TARGET_ARM、TARGET_ARM64、TARGET_LOONGARCH64、TARGET_RISCV64、TARGET_WASM见 System.Private.CoreLib.csproj而不是靠隐式约定。平台差异通过条件化Compile Include实现而非#if。同一个InMemoryAssemblyLoaderWindows 目标编译真正的实现而 Unix/Browser/WASI 目标编译一个PlatformNotSupported变体见 System.Private.CoreLib.csprojItemGroup Condition$(FeatureCominterop) ! true Compile Include$(BclSourcesRoot)\Internal\Runtime\InteropServices\ComActivator.PlatformNotSupported.cs / /ItemGroup ItemGroup Condition$(TargetsUnix) true or $(TargetsBrowser) true or $(TargetsWasi) true Compile Include$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.PlatformNotSupported.cs / Compile Include$(BclSourcesRoot)\Interop\Unix\Interop.Libraries.cs / /ItemGroup ItemGroup Condition$(TargetsWindows) true Compile Include$(BclSourcesRoot)\Internal\Runtime\InteropServices\InMemoryAssemblyLoader.cs / Compile Include$(CommonPath)Interop\Windows\OleAut32\Interop.VariantClear.cs LinkCommon\Interop\Windows\OleAut32\Interop.VariantClear.cs/Link /Compile Compile Include$(CommonPath)Interop\Windows\OleAut32\Interop.VariantChangeTypeEx.cs LinkCommon\Interop\Windows\OleAut32\Interop.VariantChangeTypeEx.cs/Link /Compile /ItemGroup这里Interop.Libraries.csUnix 动态库加载与OleAut32互操作桩Windows COM互斥进入编译——互操作代码的组织细节可进一步参考 interop guidelines。特性feature维度的开关也走构建属性而非 define例如FeatureComWrappers、FeatureCominterop、FeatureVarargs等属性控制整组源文件的进入条件属性来自 clr.featuredefines.props 的导入。从源码结构看这种“源文件级切换”与规范中 partial class 方案是互补的partial class 负责同一个类型内部的实现分叉构建系统的条件Compile负责决定哪些 partial 文件以及平台专用的整个文件参与某次编译从而让被编译出来的代码在语义上完全不包含死分支。小结跨平台代码决策清单把规范中的五个问题浓缩成一份可操作的决策清单新代码默认保持平台无关追求二进制跨平台兼容约 70% 程序集的目标必须原生差异时让 NuGet 包按平台分发不同二进制平台特定契约要么整体不提供如 Win32 注册表要么成员级抛PlatformNotSupportedException如 Unix 的Console.get_ForegroundColor并始终以“契约存在即应可用”为总目标类型内部的平台分叉用 partial class并保证存在{类型名}.cs主文件承载共享逻辑与文档命名整型平台专用用Platform*前缀partial 分平台用*.{Platform}.cs后缀并可用PLATDOC001/002等规则机器化校验构建层面优先“按平台挑选源文件”条件Compile IncludeTargets*/Feature*属性把#ifdefine 压到最小并像 SPCL 那样显式收敛DefineConstants避免 SDK 隐式常量引入不可见的行为分叉。以上原则共同构成了 dotnet/runtime 跨平台代码的治理框架文档约定管“命名与文档一致性”构建系统管“哪些代码参与编译”分发层NuGet 包管“哪个二进制去哪台机器”三者分工明确是维护大规模跨平台运行时可读性与可维护性的关键。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考