小窍门
开发软件的新手? 首先开始 学习入门 教程。 它们会在编写第一个程序时引入命名空间和 using 指令。
是否在其他语言中有经验? C# 中的命名空间的工作方式类似于 Java 中的包或 Python 中的模块。 提前了解所需的语法。
命名空间声明和 using 指令是相关的语言功能。 命名空间声明将类型置于有组织的结构中。 命名空间将相关类型组合在一起,并防止命名冲突。 指令 using 允许程序按其简单名称使用这些类型。 无需在每次使用时拼写完整的命名空间路径。
你已在编写的每个 C# 程序中使用了命名空间。 每个 .NET 类型都属于一个命名空间,文件顶部的每个 using 指令都引用一个。 例如, Console 并 Math 属于 System 命名空间,因此它们的完全限定名称是 System.Console 和 System.Math。 集合类型,如 List<T> 和 Dictionary<TKey, TValue> 属于 System.Collections.Generic。 这些命名空间中的任何一 using 个指令都允许你按其简单名称引用其所有类型。 你在任何地方使用时,编写 List<T> 而不是 System.Collections.Generic.List<T>。
本文提供了有关命名空间和 using 指令工作原理的更多背景,并演示了在 .NET 库中遇到的模式示例。
命名空间包含类型。 每个 .NET 类型都属于命名空间。 例如,请考虑 System.Threading.Tasks.Task:类型 Task 属于 System.Threading.Tasks 命名空间。
最好在同一命名空间中对相关类型或类似类型进行分组,这是 .NET 对它提供的类型执行的操作。 命名空间 System.Collections.Generic 包含与集合相关的类型, System.IO 命名空间包含与读取和写入文件、目录和数据相关的类型。 命名空间 System 包含基本类型,例如 Math, DateTime和 Console。
以下示例演示命名空间如何与 using 典型 C# 文件中的指令协同工作:
using System;
using System.Globalization;
namespace MyApp.Services;
class Greeter
{
public string Greet(string name)
{
var culture = CultureInfo.CurrentCulture;
return $"Hello, {name}! Culture: {culture.Name}";
}
}
在前面的示例中,using 指令意味着您可以通过名称 System.Globalization.CultureInfo 来使用 CultureInfo,而无需指定 System.Globalization.CultureInfo 的完整名称。 该 namespace 指令声明该 Greeter 类是命名空间的 MyApp.Services 一部分。 其完全限定名称为 MyApp.Services.Greeter.
命名空间声明
命名空间声明将类型分配给命名组。 写入的每个类型都应属于命名空间。 命名空间名称通常镜像项目的文件夹结构。 例如,文件夹中的类型 Services/Payments 通常属于 MyApp.Services.Payments 命名空间。
命名空间使用 . 运算符来表示层次结构,例如 System.Collections.Generic。 命名空间名称必须是有效的 C# 标识符名称。
文件范围的命名空间
当文件中的所有类型都属于同一命名空间时,请使用 文件范围的 语法。 在命名空间声明后添加分号,并应用于整个文件。 不需要额外的大括号或缩进:
namespace MyApp.Models;
class Customer
{
public required string Name { get; init; }
public string? Email { get; init; }
public override string ToString() => $"{Name} ({Email ?? "no email"})";
}
文件范围的命名空间可减少嵌套,并使文件更易于读取。 每个文件只能有一个文件范围的命名空间声明。
小窍门
在新代码中使用文件范围的命名空间。 大多数 .NET 模板和代码分析器都建议使用此样式。
块范围的命名空间
需要在同一文件中声明多个命名空间时,请使用 块范围的 语法。 此样式增加了额外的缩进级别。
重要
很少在同一文件中声明多个命名空间。 更常见的方案是使用 文件范围的 命名空间。
以下代码片段是 块范围的 命名空间的示例:
namespace MyApp.Models
{
class Product
{
public required string Name { get; init; }
public decimal Price { get; init; }
public override string ToString() => $"{Name}: {Price:C}";
}
}
使用指令
如果没有 using 指令,则必须使用其完全限定名来引用每种类型,即完整的命名空间路径加上类型名称。 此样式是详细、重复且难以读取的,尤其是在文件使用同一命名空间中的许多类型时:
static void ShowFullyQualified()
{
// Without a using directive, use the fully qualified name:
System.Console.WriteLine("Hello from fully qualified name!");
}
using文件顶部的指令导入命名空间,以便按其简单名称使用其类型。 以下代码片段显示了导入后的较短类型用法,这会使整个文件的引用更短且更易于阅读:
static void ShowShortName()
{
// With 'using System;' (or implicit usings enabled), use the short name:
Console.WriteLine("Hello from short name!");
}
有关详细信息,请参阅 using 指令。
全局 using 指令
using 指令仅适用于其所在的文件。 不要在每个文件中重复相同的 using 指令,而是使用 全局 using 指令,这样就可以为整个项目声明一次它们。 将它们置于任何文件中。 许多团队创建专用 GlobalUsings.cs 文件:
global using System.Text;
global using System.Text.Json;
声明全局使用后,项目中的每个文件都可以按其简单名称引用该命名空间中的类型,而无需附加 using 指令。 全局使用可删除文件之间的重复,缩小 using 每个文件顶部的块,并集中项目的命名空间策略。
隐式使用指令
对于最常见的命名空间,根本不需要编写任何 using 指令。 .NET SDK 会根据你的项目类型自动生成全局 using 指令。 通过在项目文件中设置 <ImplicitUsings>enable</ImplicitUsings> 来启用隐式使用。 例如,控制台应用项目会自动导入System、System.Collections.Generic、System.IO、System.Linq、System.Threading和System.Threading.Tasks。 你使用 dotnet new 创建的新项目会默认启用 ImplicitUsings。 新文件开头很干净,对于 using、Console 或 List<T> 这类日常类型,不再包含模板化的 Task 指令。
有关详细信息,请参阅 隐式 using 指令。
注释
本文中的其他代码示例以及整个.NET文档中的大多数示例都假定启用了隐式使用(或项目类型的等效全局用法)。 这就是为什么你在每个代码片段的顶部看不到 using System; 及类似的指令,即使代码通过其简单名称使用了 Console 或 List<T> 等类型。
静态导入指令
指令 static using 导入类型的静态成员,以便无需类型名称前缀即可调用它们:
using static System.Math;
namespace MyApp.Utilities;
class CircleCalculator
{
public static double CalculateArea(double radius) => PI * Pow(radius, 2);
public static double CalculateCircumference(double radius) => 2 * PI * radius;
}
静态用法适用于类似 Math 且 Console 经常调用的实用工具类。
类型和命名空间别名
using别名用于为类型或命名空间创建简化名称。 别名适用于长泛型类型、解决命名冲突和提高可读性:
using CustomerList = System.Collections.Generic.List<MyApp.Models.Customer>;
namespace MyApp.Services;
class CustomerService
{
public CustomerList GetTopCustomers()
{
CustomerList customers = [new() { Name = "Alice" }, new() { Name = "Bob" }];
return customers;
}
}
从 C# 12 版本开始,你可以为任何类型创建别名,包括元组和指针类型:
using Point = (double X, double Y);
namespace MyApp.Geometry;
class Shape
{
public static double Distance(Point a, Point b)
{
var dx = a.X - b.X;
var dy = a.Y - b.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
对于两个程序集定义相同的完全限定类型名称的更高级方案,请使用 外部别名 来消除它们之间的歧义。