首页 / C# 入门教程 / static 静态成员

C# 入门教程

static 静态成员

本教程共 100 篇 · 第 33 篇 · 更新于 2026-07-31 · 约 12 分钟阅读

C#C# 入门教程static静态成员面向对象

33. static 静态成员

本节目标:学完你能分清静态成员和实例成员,并在合适场景用 static

前面写的字段和方法都挂在「对象」上:每 new 一个,就有一份独立的数据。而 static(静态)成员属于「类本身」,无论 new 多少个对象,它都只有一份,用类名直接访问,不靠对象。

静态字段:全员共享一份

静态字段(static field)用 static 修饰,属于类。所有实例读写的都是同一块内存。适合放「计数器」「配置开关」这类全局共享的数据。

namespace CSharpDemo;

class Counter
{
    public static int Total;     // 静态:属于 Counter 类
    public int Id;               // 实例:每个对象各一份

    public Counter() => Total++;
}

new Counter();
new Counter();
Console.WriteLine(Counter.Total);   // 2,所有实例共享
Note

访问静态成员用「类名.成员」,而不是「对象.成员」。写 Counter.Total 才是对的。

静态方法:没有 this 的工具

静态方法(static method)同样属于类,里面不能碰实例字段,也用不了 this。它更像一批独立工具,例如 Math.SqrtConsole.WriteLine

namespace CSharpDemo;

class Calculator
{
    public static int Add(int a, int b) => a + b;
    public static decimal Tax(decimal price) => price * 0.13m;
}

Console.WriteLine(Calculator.Add(3, 5));      // 8
Console.WriteLine(Calculator.Tax(9.99m));     // 1.2987

Calculator 完全不需要 new,直接用类名调用,干净利落。

静态类:只能装静态成员

当一个类全是工具方法、压根不该被实例化,就把它声明为静态类(static class)。静态类不能 new,且隐式是 sealed(不能被继承)。你天天用的 MathConsole 正是如此。

namespace CSharpDemo;

static class MathUtil
{
    public static int Max(int a, int b) => a > b ? a : b;
}

// var m = new MathUtil();   // 报错:静态类不能实例化
Tip

判断要不要静态类:如果一类东西「不需要保存状态、只是纯计算或纯操作」,做成静态类最省心。

静态构造函数:只跑一次

静态字段想做复杂初始化时,用静态构造函数(static constructor)。它写 static 类名(),无参数无修饰符,在「类第一次被用到之前」自动执行,且整个程序生命周期只跑一次。

namespace CSharpDemo;

class Config
{
    public static string Mode;
    static Config()
    {
        Mode = "生产环境";
        Console.WriteLine("静态构造:配置已加载");
    }
}

Console.WriteLine(Config.Mode);
Console.WriteLine(Config.Mode);   // 只打印一次「配置已加载」
Note

静态构造没有访问修饰符,也不能带参数。它负责给静态成员「一次性」做初始化,典型场景是读取配置、建连接池。

实例成员 vs 静态成员

两者最易混,记住这张对照表:

对比项实例成员静态成员
属于某个对象整个类
访问方式对象.成员类名.成员
内存每对象一份全局一份
能否用 this不能
能否访问对方实例可访问静态静态不能访问实例
namespace CSharpDemo;

class Shop
{
    public static string Name = "码上小店";  // 静态
    public decimal Price = 9.99m;            // 实例

    public void Show()
    {
        Console.WriteLine(Name);     // 实例方法能读静态
        Console.WriteLine(Price);
    }
}
Tip

新手常踩的坑:在静态方法里直接写实例字段名,编译器立刻报错。静态方法「看不见」具体哪个对象,自然拿不到实例数据。

什么时候用静态

  • 工具箱:MathCalculator 这类纯函数。
  • 全局状态:对象计数、应用级配置。
  • 单例入口:程序里唯一的日志器、配置中心。
  • 不适合:需要保存每个对象各自状态的数据,那必须用实例成员。

静态成员存在哪里

通俗地说,静态字段不属于任何一个对象,而是挂在「类型本身」上。程序启动时类型被加载,静态数据就分配好;直到程序退出才释放。这解释了为什么所有对象看到的是同一份,也解释了为什么它不随某个对象被回收而消失。

Note

正因生命周期长,静态字段适合放全局共享、且基本不变的数据;若放会频繁改动的状态,容易引发预料之外的跨对象干扰。

实例构造与静态构造的先后

当类同时有静态构造和实例构造,顺序是:先跑静态构造(类首次使用前,只一次),再跑实例构造(每次 new 都跑)。

namespace CSharpDemo;

class Sample
{
    public static int A = Init();          // 静态字段初始化
    static Sample() => Console.WriteLine("静态构造");
    public Sample() => Console.WriteLine("实例构造");
    static int Init() { Console.WriteLine("静态字段初始化"); return 0; }
}

new Sample();   // 依次:静态字段初始化 → 静态构造 → 实例构造

静态类与单例模式

静态类最省事,但它「全局只有一份且无法替换」,测试时不便模拟。若你需要「全局唯一、但又能通过接口替换」的对象(如日志器),常用单例模式(一个普通类 + 静态实例字段)代替纯静态类。

Tip

新手踩坑:把本该是对象状态的数据塞进静态字段,结果多线程或多次测试互相污染。能用实例就别用静态。

常见陷阱:静态状态的隐蔽副作用

静态字段是「全局变量」的优雅说法。一处修改,处处可见,排查时往往追不到源头。除非真的需要共享,否则优先用实例字段。

静态类的几条硬限制

静态类不是「随便装着静态成员的类」,它有几条死规矩:不能实例化(new 报错)、隐式 sealed(不能被继承)、也不能实现接口。它只能包含静态成员。这些限制保证静态类纯粹是「工具箱」,不会变成某个对象。

// static class Tools : IFoo { }   // 错误:静态类不能继承/实现接口
Note

记住:想让一个类既能实例化又有静态工具方法,不必声明成静态类——普通类里混放静态成员完全合法。静态类只用于「纯粹工具、永不实例化」的场景。

用静态字段做跨实例计数

静态字段最适合统计「一共创建了多少对象」这类全局事实。因为所有实例共享它,每次构造都自增,最后读出来就是总量。

namespace CSharpDemo;

class User
{
    public static int Count;
    public User() => Count++;
}

new User(); new User(); new User();
Console.WriteLine($"共创建 {User.Count} 个用户");   // 3

静态方法里的重载与扩展

静态方法照样能重载(同名不同参数),也常见被做成扩展方法的载体。但无论怎么写,它都碰不到具体某个对象的字段——那是实例方法的领地。

Tip

判断一条数据该静态还是实例:问「它是每个对象各不相同,还是所有对象共享同一份?」各不同用实例,共享用静态。

小结

static 成员属于类而非对象,用类名直接访问且全局只有一份,生命周期随程序。静态字段共享数据,静态方法做无状态工具,静态类装纯工具且不能实例化,还自带不可继承、不可实现接口的限制。静态构造函数在类首次使用前跑一次,用来初始化静态数据。关键分水岭:实例成员靠对象、有 this;静态成员靠类、无 this、碰不到实例数据。不要把实例状态误放进静态字段,否则会造成跨对象污染。