Menu

C# using 指令与命名空间:别名、using static、global using

C# 命名空间如何组织类型,using 指令又如何导入它们:别名、using static、完全限定名、解决不明确的引用、全局 using 以及文件范围的命名空间。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

.NET 中的每个类型都有一个完整名称,由命名空间和类型名组成:System.Console、System.Collections.Generic.List<T>、System.Text.StringBuilder。命名空间把相关的类型归为一组,避免名称冲突,所以你自己的 Order 或 Timer 类可以和库中的同名类共存。文件顶部的 using 指令会导入一个命名空间,让你可以使用短名称。

输出:

Cart: tea, honey
1

using 指令只在编译时影响名称。它不会加载库,也不会往程序里加代码;哪些库可用,是由项目的引用和 NuGet 包决定的。如果编译器报告某个类型或命名空间“could not be found (are you missing a using directive or an assembly reference?)”,即错误 CS0246,那么要么是缺了 using 行,要么是没有引用相应的包。

声明你自己的命名空间

把类型放进一个 namespace 块中。命名空间可以嵌套,用点号书写,同一个命名空间也可以分散在多个文件里。

输出:

120.00 via DHL
Shop.Billing.Invoice

namespace Shop.Billing { } 是 namespace Shop { namespace Billing { } } 的简写。Shop 内部的代码可以不加 Shop. 前缀直接引用 Billing.Invoice,因为名称会先在外层命名空间中查找。

惯例:以公司或产品名开头(Contoso.Payments),接着是功能或层级,并且与文件夹结构对应(Contoso.Payments/Refunds/RefundService.cs 中放的是 Contoso.Payments.Refunds)。永远不要让类和它的命名空间同名(Shop.Shop),否则到处都得写别扭的限定名。

别名

using Name = Namespace.Type; 给一个类型(或命名空间)起一个本地名称。有两种情况需要它:缩短很长的泛型类型,以及解决名称冲突。

输出:

latte: 3.60
order 17

别名只在声明它的文件中生效(除非它是 global 的,见下文)。在 C# 12 及更高版本中,别名可以指代任何类型,包括元组和数组:using Point = (int X, int Y);。

不明确的引用(CS0104)

当两个已导入的命名空间中都有同名类型时,使用短名称就会报错。典型的例子是 Timer,它同时存在于 System.Threading、System.Timers 和(桌面应用中的)System.Windows.Forms:

using System.Threading;
using System.Timers;

var t = new Timer(1000);
// error CS0104: 'Timer' is an ambiguous reference between
// 'System.Timers.Timer' and 'System.Threading.Timer'

三种修复方法,影响范围从小到大:写出完全限定名(new System.Timers.Timer(1000)),添加一个别名说明你指的是哪一个,或者删除你不需要的那条指令。

输出:

250
System.Timers.Timer
System.Threading.Timer

如果你自己的命名空间里定义了名为 System 或 Console 的类型,前缀 global:: 会强制从根命名空间开始查找:global::System.Console.WriteLine("hi");。正因如此,生成的代码到处都在用它。

using static

using static 导入单个类型的静态成员,这样调用它们时就不用写类型名。它适合反复调用同一个类的代码,比如 Math:

输出:

Area: 28.27
Hypotenuse: 5
42

using static 也适用于枚举(using static System.DayOfWeek; 让你可以直接写 Monday)以及你自己的静态类。在短名称不会引起歧义的地方使用它;在一个大文件里单独出现的 WriteLine,可能会让读者搞不清它到底是哪个输出对象的。

global using 与文件范围的命名空间(C# 10)

C# 10 的两个功能减少了文件顶部的样板代码。全局 using 作用于项目中的每个文件,通常集中放在一个文件里,比如 GlobalUsings.cs:

// GlobalUsings.cs, C# 10 and later
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Money = System.Decimal;

.NET 6 及更高版本的 SDK 项目可以替你生成这些指令。在 .csproj 中设置 <ImplicitUsings>enable</ImplicitUsings>(新模板的默认设置)后,控制台项目会得到 System、System.Collections.Generic、System.IO、System.Linq、System.Net.Http、System.Threading 和 System.Threading.Tasks;Web 项目还会得到 ASP.NET Core 的命名空间。这就是为什么现代的 Program.cs 文件一开头就是代码。

文件范围的命名空间用一个分号而不是代码块,为整个文件声明命名空间,省去一层缩进:

// C# 10 and later
namespace Shop.Billing;

public class Invoice
{
    public decimal Amount { get; set; }
}

一个文件只能有一个文件范围的命名空间,而且不能在同一个文件中与块形式的命名空间混用。较老的项目、Unity 脚本以及面向 .NET Framework 的代码使用前面展示的块形式。

using 指令放在哪里,以及 using 语句

using 指令必须出现在文件中所有命名空间或类型声明之前(全局 using 要在最前面)。它们也可以放在某个命名空间块内部,这时只在那里生效;各家的代码风格指南说法不一,大多数 .NET 代码把它们放在顶部。

关键字 using 在方法内部还有第二个毫不相干的用途:using 语句 using (var file = File.OpenRead(path)) { ... } 会在代码块结束时调用对象的 Dispose,关闭文件和连接。两者只是共用一个关键字,别无关联。参见 using 语句页面。

常见问题

C# 中 using 的作用是什么?

在文件顶部,using System.Text; 是一条导入命名空间的指令,这样你就可以写 StringBuilder,而不用写 System.Text.StringBuilder。它不会增加任何代码,也不会加载任何东西,只是让名称变短。在方法内部,using (var f = ...) 是另一个功能,即 using 语句,它会在代码块结束时释放一个对象。

C# 中的命名空间是什么?

命名空间是一组有名字的类型集合,用来避免名称冲突并组织代码,比如 System.Collections.Generic 或 MyShop.Billing。两个类只要位于不同的命名空间,就可以同名。按照惯例,命名空间与文件夹结构对应,并以公司名或产品名开头。

C# 中的 using static 是什么?

using static System.Math; 导入某一个类型的静态成员,这样你就可以直接调用 Sqrt(16)、读取 PI,而不用加 Math. 前缀。它适用于任何类,包括你自己的类和枚举。在短名称依然清晰的地方使用它,比如大量数学运算的代码。

C# 中如何解决“is an ambiguous reference”错误?

错误 CS0104 表示两个已导入的命名空间中都有一个同名类型,例如 System.Threading 和 System.Timers 中都有 Timer。写出完全限定名(System.Timers.Timer),删掉其中一条 using 指令,或者添加一个别名:using Timer = System.Timers.Timer;。

C# 中的 global using 是什么?

global using System.Text;(C# 10)为项目中的每个文件导入一个命名空间,而不仅仅是它所在的那个文件。.NET 6 及更高版本的 SDK 项目在设置了 <ImplicitUsings>enable</ImplicitUsings> 时,会自动生成一组全局 using,这就是为什么新建的 Program.cs 文件里没有 using 行。

Coddy 编程语言插图

和 Coddy 一起学编程

开始