File-scoped Namespaces · Global Usings · Constant Interpolated Strings——消灭项目中每个文件都重复的样板
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MyConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello");
}
}
}
Console.WriteLine("Hello, World!");
Top-level Statements(C# 9)消掉了 class Program 和 Main。但那些 using 去哪了?namespace 去哪了?——这就是 C# 10 的答案。
// 传统:所有代码被 namespace 大括号包着——全体缩进 4 格
namespace MyApp.Services
{
public class OrderService // ← 4 格
{
public async Task<Order> GetAsync(int id) // ← 8 格
{
// ... 真正的代码 ← 12 格
}
}
}
// C# 10:分号结尾,不缩进
namespace MyApp.Services;
public class OrderService // ← 顶格写
{
public async Task<Order> GetAsync(int id)
{
// ... ← 少一级缩进
}
}
纯粹的语法糖——编译产物完全一样。一个文件一个 namespace 是 99% 的场景,为什么要为这 99% 多缩进一级?
namespace A { namespace B { } })不能用此语法。
.NET 6 的 Program.cs 连 namespace 都没有写——但编译器仍然把它放进了命名空间。这是 SDK 的隐式命名空间Implicit Namespace:
// 你看到的 Program.cs:
Console.WriteLine("Hello");
// 编译器实际编译的等价代码:
// namespace MyApp; ← SDK 根据 .csproj 的 RootNamespace 自动注入
// internal partial class Program {
// private static void $Main(string[] args) {
// Console.WriteLine("Hello");
// }
// }
根命名空间来自 .csproj 中的 <RootNamespace> 或项目文件名。只有 Top-level Statements 文件且没有显式 namespace 声明时,隐式命名空间才生效。
// C# 10 之前——每个 .cs 文件都以这堆开头:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
// ... 重复 N 次
// C# 10——GlobalUsings.cs 一个文件管整个项目:
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Microsoft.Extensions.Logging;
// 其他 .cs 文件不再需要这些 using
// ① 基本:整个项目可见
global using System.Text.Json;
// ② global + static:全局静态导入
global using static System.Math; // 所有文件都能直接用 Abs(), PI
// ③ global + alias:全局别名
global using MyDict = Dictionary<string, object>;
GlobalUsings.cs vs .csproj <Using>GlobalUsings.cs 推荐 | .csproj <Using> | |
|---|---|---|
| 可见性 | Solution Explorer 里一眼看到 | 藏在 .csproj 里,多数人不点开 |
| 新人上手 | 打开文件立刻知道全局有什么 | 没人告诉就不知道这里藏了 using |
| Code Review | PR 里改这个文件很明显 | .csproj 改动容易被忽略 |
| 编译产物 | 完全一样——都生成程序集级 using 指令 | |
<Using> 主要是 SDK 的隐式导入在用——那是 SDK 的活,不是你该管的。
.NET 6+ SDK 根据项目类型自动注入一套 global using:
// 控制台/类库 → SDK 自动注入:
// System, System.Collections.Generic, System.IO,
// System.Linq, System.Net.Http, System.Threading,
// System.Threading.Tasks
// ASP.NET Core → 额外注入:
// Microsoft.AspNetCore.Builder,
// Microsoft.Extensions.DependencyInjection,
// Microsoft.Extensions.Hosting,
// Microsoft.Extensions.Logging, ...
隐式导入是 SDK 自动生成的 global using——机制相同,来源不同。你自己的 global using 会叠加在上面。通过 <ImplicitUsings>disable</ImplicitUsings> 可关闭。
// C# 9:const + 插值 = 编译错误
const string Base = "https://api.example.com";
const string Ver = "v1";
const string Url = $"{Base}/{Ver}"; // ❌ C# 9: 编译错误
const string Url = Base + "/" + Ver; // ✅ 只能用 +
// C# 10:所有插值部分都是常量 → 合法
const string Url = $"{Base}/{Ver}"; // ✅ 编译时求值 → "https://api.example.com/v1"
| 规则 | 示例 |
|---|---|
| 所有插值部分必须是常量 | $"pi = {Math.PI}" ❌——Math.PI 是静态字段,不是 const |
| 可以用数字常量 | $"v{3}" ✅ |
可以用 nameof() | $"Class:{nameof(MyClass)}" ✅——nameof 是编译时常量表达式 |
| 零运行时开销 | IL 直接是最终拼接好的字符串——和手写一样 |
// ✅ 真实场景——URL 模板、日志前缀、连接名
const string BaseUrl = "https://api.example.com";
const string UsersEndpoint = $"{BaseUrl}/users";
const string AppName = "MyApp";
const string ConnName = $"{AppName}Db"; // "MyAppDb"
// ===== Program.cs (.NET 6 console) =====
// 你看不到 using(隐式导入 + GlobalUsings)
// 你看不到 namespace(隐式命名空间 = SDK 自动注入的 File-scoped Namespace)
Console.WriteLine("Hello, World!"); // Top-level Statements (C# 9)
// ===== ASP.NET Core Minimal API 全貌 =====
// GlobalUsings.cs 里有:
// global using Microsoft.AspNetCore.Builder; ...
namespace MyApi; // File-scoped (C# 10)
var builder = WebApplication.CreateBuilder(args); // Top-level (C# 9)
builder.Services.AddSingleton<Database>(new()); // Target-typed new (C# 9)
var app = builder.Build();
app.MapGet("/", () => "Hello");
app.Run();
从 11 行样板到 1 行业务逻辑——不是某个特性特别强,是 C# 9 + 10 组合起来消灭了几乎所有的仪式代码。
const string s = $"{nameof(MyClass)}_v3"; 能编译吗?GlobalUsings.cs,把高频 using 集中进去。找出所有 namespace X { } 大括号包裹的单 namespace 文件,改成 namespace X;。