关于对 C# 中 ImplicitUsings,GlobalUsings 的讨论

关于对 C# 中 ImplicitUsings,GlobalUsings 的讨论 微软 SDK 团队的 Tim Heuer 提出了一个想法从 .NET 10 开始把 enable 和 enable 从项目模板移入 SDK 核心 targets 中让它们变成真正意义上的“默认开启且无需在 .csproj 中显式出现”。他的理由是自 .NET 6 起所有模板都默认写了这两行对现代框架来说已是“不必要的样板”.NET 10 引入了单文件运行dotnet run app.cs和 dotnet project convert新手从一个 .cs 文件转成项目时突然冒出来的属性会成为“学习绊脚石”SDK 应该更“武断opinionated”一点推动现代 C# 开发的前进方向。读到这里我心里警铃大作——如果这落地了未来想关闭隐式 using 的阻力会更大甚至很多开发者会根本不知道这个开关的存在。围观大佬们的“掰头”点开讨论区我发现社区并不是一边倒地鼓掌反而有不少资深开发者提出了尖锐的反对意见这也成了我坚定立场的关键参考。反对派的声音我站这边TheBuzzSaw2025-05-29直言不认同 ImplicitUsings每个新项目都会关掉它。他认为“我不想要在每个文件里都生效的命名空间也不想让名字以不显眼的方式突然冲突”。在他看来csproj 里那一行的存在不是样板而是对项目重要决策的显式声明。londospark2025-05-30附议上述观点觉得“知道类型从哪来非常有用”开启 ImplicitUsings 是用极小收益换取 Bug 的温床。好用的 IDE 本来就会自动补 using没必要把这个认知负担转嫁给读者。Frulfump2025-05-30进一步指出隐患一旦 ImplicitUsings 默认开启开发者容易在 GlobalUsings.cs 里无脑加更多命名空间导致“黑盒依赖”问题恶化。而且在 GitHub / Azure DevOps 做 PR Review 时我们往往不在 IDE 里看不到悬停提示文件顶部的显式 using 才是代码自描述的底线。提案方的顾虑与妥协Tim Heuer 也承认这会带来升级破坏性尤其是那些“删掉了属性而非显式设 disable”的旧项目。他提到可能通过 if (TFM net10.0) 之类的条件来缓解但核心目的仍是“清理模板噪音让默认更隐式”。我的复盘为什么“隐式”在这里不成立结合大佬们的讨论回头看 C# 10 以来一直被宣传的 ImplicitUsings我在 C# 14 / .NET 10 的项目里重新评估了它结论是关掉它。“Explicit is better than implicit”不是废话当一个 .cs 文件顶部没有 using System; 时你失去的是一眼看清依赖的能力。几个月后回看代码或者新人接手没法从文件本身推断它依赖了哪些命名空间。对比一下// 隐式Path 来自 System.IO还是某个第三方库的 Pathvar p Path.Combine(a, b);// 显式依赖一目了然using System.IO;var p Path.Combine(a, b);2. 命名空间冲突的隐性炸弹System.IO.Path vs iTextSharp.text.pdf.parser.Path当项目引用变多隐式 using 会让你在不知情的情况下绑定到某一个。编译器会选但你未必想要那个选择。显式声明把歧义在书写时就暴露出来。obj/*.GlobalUsings.g.cs 是看不见的文件ImplicitUsings 的本质是 SDK 在 obj/ 下生成一个全局 using 文件。没人会日常去翻 obj/CtrlShiftF 也搜不到这些依赖声明。把依赖知情权交给一个构建中间文件是对可维护性的透支。默认注入的比你以为的多以 Microsoft.NET.Sdk 为例默认隐式注入的包括System, System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading, System.Threading.TasksWeb / Worker SDK 还会追加一大把 Microsoft.AspNetCore.* 和 Extensions.*。你可能只是写个简单控制台却在无意中调用了 LINQ 或 HttpClient自己毫无察觉——直到迁移到一个没有隐式 using 的环境编译崩塌。我的最终方案关掉 ImplicitUsings但合理使用 GlobalUsings显式版。第一步关开关disable 第二步手写一个受控的 GlobalUsings.cs 不要靠 SDK 魔法自己在项目根目录建一个 GlobalUsings.cs只放你审过的、真正想全局共享的命名空间// GlobalUsings.csglobal using System;global using System.Collections.Generic;global using System.IO;global using System.Threading.Tasks;// 刻意不放 System.Linq、System.Net.Http —— 这两个我希望在用到时显式声明这样做的好处是✅ 依赖集合集中、可见、可审查✅ 没有 obj/ 下的黑盒生成文件✅ 仍然减少部分文件顶部噪音但在完全可控的前提下✅ 就算 .NET 10 的 #49182 落地你的项目已经站在清晰的一边如果某些 SDK 默认会隐式引入你不想用的也可以在 csproj 里精确剔除结语我的立场 Tim Heuer 的提案出发点是好的——降低新手门槛、减少模板噪音。但用“隐式”解决“显式带来的认知负担”本质上是在把成本从“写代码的人”转移给“读代码和排错的人”。在 #49182 的讨论里TheBuzzSaw、longspark 这些老手的反对让我确认了一件事ImplicitUsings 作为可选特性没问题但作为默认方向我不买账。代码的“简洁”是写给此刻的自己看的代码的“清晰”是写给未来的自己和同伴看的。两者冲突时我选清晰。所以在我的 C# 14 / .NET 10 项目里ImplicitUsings → disableGlobalUsings.cs → 手写、受控、审查后使用