该技能用于在 .NET 互操作边界处理 F# 可空性检查。启用方式是在项目文件中设置 <Nullable>enable</Nullable> 与 <TreatWarningsAsErrors>true</TreatWarningsAsErrors>;前者相当于传入 --checknulls+,可空性诊断 FS3261、FS3262 默认是警告,配合 TreatWarningsAsErrors 会直接导致构建失败。
核心思路:对每个 T | null 只在边界处转换一次,之后代码统一使用 T 或 T option。常见返回 T | null 的成员包括 JsonElement.GetString()、JsonSerializer.Deserialize<T>、Uri.TryCreate 的 out 值、Environment.GetEnvironmentVariable、exn.InnerException、Path.GetDirectoryName、TextReader.ReadLine()、Console.ReadLine() 等。处理方式可用 match 判断 null 给回退值、用 Option.ofObj 转 option、用 match 转 Result,或在 Try 方法中配合 NonNull 模式匹配。
需要注意的规则:isNull 和 String.IsNullOrEmpty 不会把 string | null 收窄为 string,应改用 match;不要把 | null -> 与 | NonNull -> 混用,否则触发 FS3262 或 FS0025;公共返回类型应显式标注,否则 T | null 会泄漏给调用方;string 绑定不能持有 null,应声明为 string | null 或改用 string option。
存在编译通过但会泄漏 null 的写法:无类型标注的 Some(reader.ReadLine()) 会得到 Some null;对 string | null 使用 string x 会把缺失值变成空字符串;nonNull 会抛 NullReferenceException;Unchecked.nonNull 把 null 传递到更晚的位置;#nowarn "3261" 或 NoWarn 会隐藏后续所有 null 警告。nonNull / Unchecked.nonNull 仅在 API 明确不会返回 null 且类型未体现时使用,并需在同行或上一行写明原因,不应在文件或项目级别压制 FS3261。
关于 FS0041:.NET 9 为 TimeSpan.FromSeconds、FromMinutes、FromMilliseconds、FromMicroseconds 增加了 int64 重载,int 实参会匹配不到精确重载而报错;可在调用处把参数转为 float,例如 TimeSpan.FromSeconds 5.0,不要为消除该错误而修改公共参数或记录字段类型。
验证方式:保持 Nullable 与 TreatWarningsAsErrors 开启且不添加 NoWarn 构建,检查改动代码中的 nonNull、#nowarn、Some 及以函数方式使用的 string,并用 null 输入在各边界运行。可运行 pwsh ./scripts/run.ps1 执行随附证明。