Menu

C# static 关键字:静态类、静态方法、字段与构造函数

C# 中 static 是什么意思:属于类型而不属于对象的成员,在实例之间共享的状态,静态方法和静态类,静态构造函数,const 与 static readonly,using static,以及 CS0120 错误。

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

static 标记的成员属于类型,而不属于某个对象。实例字段每个对象各有一份;静态字段则只有一份,仅此而已。实例方法在对象上运行(order.Ship());静态方法在类型上运行(Math.Round(x)),根本不需要对象。

静态字段:只有一份,共享使用

最能看清区别的例子是计数器。每个 Order 都有自己的 Id,但目前为止已经创建了多少个订单,这个计数必须保存在一个共享的地方。

输出:

Ana #1, Ben #2, Chen #3
Orders created: 3

静态成员通过类型名访问:写 Order.Created,而不是 a.Created。C# 不允许通过实例访问静态成员(错误 CS0176),这让调用处一眼就能看出这个值是共享的。

静态方法

静态方法没有 this。它只能使用自己的参数和其他静态成员。这让它天然适合用来写根据输入计算结果的辅助函数:

输出:

61.38 EUR
7

Main 本身就是静态的:运行时在任何对象存在之前就调用它。

CS0120 错误:静态代码使用实例成员

因为 Main 是静态的,初学者往往一添加第二个方法就会遇到这个错误:

class Program
{
    int visits;
    void Greet() { Console.WriteLine("Hello"); }

    static void Main()
    {
        Greet();    // error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
        visits++;   // error CS0120: An object reference is required for the non-static field, method, or property 'Program.visits'
    }
}

静态方法没有对象,所以也就没有可供它递增的 visits。修复方法有两种,哪种正确取决于这个成员是什么:

  • 这个成员确实是每个对象各自的数据:创建一个对象并使用它。var app = new Program(); app.Greet();
  • 这个成员不需要对象状态:把它标记为 static。static void Greet() { ... }

为了让错误消失而把所有东西都标记为 static,在很小的程序里行得通,到了真实的程序中就会变成全局状态。判断的方法是问自己:这份数据属于某一个对象,还是属于整个程序?

反方向则总是允许的:实例方法可以随意调用静态成员。

静态类

声明为 static 的类只能包含静态成员,而且永远不能被实例化。它就是一组有名字的函数和常量。

输出:

77
100
True

编译器会强制执行这个约定:静态类中的实例成员会报错误 CS0708,对它使用 new 会报错误 CS0712。Math、Console、File、Path 和 Enumerable 都是静态类。扩展方法也必须放在静态类中。

静态构造函数与 static readonly

如果一个静态字段的值需要不止一个表达式才能算出来,可以在静态构造函数中设置它。运行时会在类型第一次被使用之前运行它,而且只运行一次:

输出:

Before first use
ShippingRates initialized
5.50
3
2026-01-01

readonly 表示这个字段只能在它的初始化器或静态构造函数中赋值。之后它就不能被重新赋值,但它指向的对象仍然可能是可变的,所以这个字典以 IReadOnlyDictionary 的形式公开。

const 与 static readonly

两者都能提供一个由整个程序共享的命名值,区别在于这个值是在什么时候确定的:

conststatic readonly
值的确定时机编译时运行时,一次
允许的类型数字、char、bool、string、枚举、null任何类型
隐式静态是(不能写 static const)需要显式声明为 static
复制到调用方程序集中是否,从字段读取
可用于 switch 的 case 和特性实参是否

“复制”这一行对库来说很重要:如果程序集 A 声明了 public const int MaxItems = 50;,而程序集 B 使用了它,那么 B 中包含的是字面量 50。把 A 中的值改成 100,在 B 重新编译之前对 B 没有任何影响。对于可能在版本之间变化的值,请优先使用 static readonly。

using static

using static 指令(C# 6)会导入某个类型的静态成员,这样你调用它们时就不用写类型名了:

输出:

Area: 28.27
40

在大量数学运算的代码中它读起来很顺。在其他地方要少用:单独一个 WriteLine 或 Parse,会让人看不出这个方法来自哪个类型。

静态状态与多线程

静态字段由进程中的每个线程共享。多个线程同时递增同一个静态字段会丢失更新,因为 created++ 包含读取、相加和写入三步,两个线程可能会交错执行它们。计数器请使用 Interlocked.Increment(ref created),更复杂的情况请使用 lock。这个问题最常出现在 Web 应用中:每个请求都运行在某个线程上,所以一个保存“当前用户”的静态字段会被所有用户共享。

还有一个细节:在泛型类中,每个构造出来的类型都有自己的静态字段。Cache<int>.Count 和 Cache<string>.Count 是两个不同的字段。

什么时候使用 static

适合的用法:

  • 纯辅助函数,根据实参计算结果:格式化、转换、校验。
  • 由整个程序共享的常量和只读查找数据。
  • 工厂方法,例如 TimeSpan.FromMinutes(5),或者你自己的 Temperature.FromCelsius(21)。
  • 扩展方法。

不适合的用法:

  • 可变的全局状态(当前用户、购物车、会变化的设置)。它会把所有接触它的代码耦合在一起,还会让测试依赖于彼此留下的数据。
  • 任何你在测试中想要替换的东西,例如时钟、数据库或邮件发送器。静态调用无法被替换成假对象;而通过构造函数传入的实例可以。

常见问题

C# 中 static 是什么意思?

static 成员属于类型本身,而不属于任何对象。无论存在多少个对象,静态字段都只有一份;静态方法通过类型名调用(Math.Max(3, 7)),不需要创建实例。静态方法没有 this,所以不能直接使用实例字段。

C# 中的静态类是什么?

声明为 static 的类只能包含静态成员,而且永远不能被实例化(对它使用 new 会报错误 CS0712)。它用来把不需要对象状态的相关函数组织在一起,例如 Math、Console 或 File。扩展方法必须声明在静态类中。

如何修复“An object reference is required for the non-static field, method, or property”错误?

这是错误 CS0120:一个静态方法(通常是 Main)在使用实例成员。要么创建一个对象并通过它调用这个成员(var app = new Program(); app.Run();),要么在这个成员不需要每个对象各自的数据时,把它标记为 static。

C# 中 const 和 static readonly 有什么区别?

const 是编译期常量(数字、字符串、bool、枚举),它的值会被复制到每个使用它的程序集中。static readonly 字段在运行时赋值一次,赋值发生在它的初始化器或静态构造函数中,可以保存任何类型,例如 DateTime 或列表。永远不会改变的值用 const,其他情况用 static readonly。

C# 中什么时候应该使用 static?

只处理参数的代码(例如价格格式化这样的纯辅助函数)、对整个程序真正全局的常量和缓存,以及工厂方法,都适合用 static。凡是保存每个用户或每个请求各自状态的东西,或者你在测试中想要替换掉的东西,都不要用 static:共享的可变状态难以测试,而且不加锁的话在多线程下也不安全。

Coddy 编程语言插图

和 Coddy 一起学编程

开始