先一针见血指出你的误区
csharp
public static partial class NtsHelper
{
private static readonly GeometryFactory _factory = new GeometryFactory();
private const double Tolerance = 1e-6;
public static double GetTolerance()
{
return Tolerance;
}
}
这里
_factory、Tolerance根本就不是「非静态成员」!它们两个全部都是静态成员。
逐条拆解每个字段
1. private static readonly GeometryFactory _factory
- 修饰符:
static
👉 静态字段 ,属于类NtsHelper,不属于任何实例对象。
readonly只是代表:只能在构造阶段赋值一次,之后不可修改 ,readonly ≠ 实例成员!
很多人踩坑:以为
readonly= 实例变量。
static readonly→ 静态只读(全局唯一)readonly(不带static)→ 实例只读(每个new出来的对象一份副本)
2. private const double Tolerance = 1e-6;
const 常量天生就是隐式静态的 !
哪怕你不写 static,编译器也会把它当成静态。
常量永远属于类,不属于对象实例。
所以下面静态方法
GetTolerance()直接 return Tolerance,完全合法。
3. GetTolerance()
标记了 static,静态方法。
静态方法只能直接访问本类的静态成员,你这里刚好符合规则。
对比两张对照表,彻底分清
✅ 你代码里(全部静态)
csharp
//静态只读字段:属于类
private static readonly GeometryFactory _factory;
//常量,天生隐式静态
private const double Tolerance = 1e-6;
//静态方法
public static double GetTolerance(){}
❌真正的非静态(实例)成员,长这样(没有static)
csharp
public partial class NtsHelper
{
//实例字段:必须 new NtsHelper() 创建对象之后才有
private readonly GeometryFactory _factory;
private double _myValue;
//实例方法
public double GetValue()
{
return _myValue;
}
}
⚠️ 注意!
static class静态类,禁止出现任何不带static的实例成员。如果你在静态类写:
csharp
public static partial class NtsHelper
{
private readonly GeometryFactory _factory; //编译报错!!
}
编译器直接红色报错,静态类不能包含实例字段。
回到你项目设计的优点(管网NTS工具类)
csharp
private static readonly GeometryFactory _factory = new GeometryFactory();
_factory 全局只实例化一次 ,程序启动就创建好。
所有地方调用NTS几何计算复用同一个GeometryFactory对象,不用每次计算都new一个工厂,节省内存、性能更好。
快速记忆口诀
static readonly:静态只读,一份全局副本。readonly:实例只读,每个对象一份。const:天生隐式静态,不需要写static。- 静态类里面所有成员必须带static,编译器强制要求。
核心结论先给你,把3条功能责任分清
全局唯一:
static的功劳只能构造阶段赋值一次 + 运行后不能修改:
readonly的功劳
readonly 确实是「只读」,但不是编译期常量 ,它是「运行时只读锁」。
下面拆开逐个讲清楚。
一、readonly到底是什么?能干2件事
功能1:赋值时机受限
字段只能在两个地方赋值:
- 声明的时候直接初始化:
public readonly double a = 10; - 类的构造函数里面(实例构造函数 / 静态构造函数)
构造执行完毕之后,代码任何地方再也不能给这个字段赋值、修改它。
功能2:运行期间禁止改写
构造跑完,你写 xxx = 5; 修改readonly字段,编译器直接报错。
⚠️ 非常重要的误区:
readonly本身完全不控制"全局唯一" 。如果你只写
readonly、不加static,每new一个对象,就会生成一份全新副本!
实例‑readonly(不带static)例子
csharp
class Demo
{
//实例只读字段:每new一个对象,就有一份独立副本
public readonly double Value;
public Demo(double v)
{
Value = v; //✅构造函数内允许赋值
}
}
var d1 = new Demo(10);
var d2 = new Demo(20);
//d1.Value 和 d2.Value 是两份完全独立的数据!
这里只有readonly,没有static,不是全局唯一!
二、static 干的是什么?
static 的职责只有一件事:这个字段属于【类本身】,而不属于new出来的对象实例
- 整个程序内永远只有1份副本,全局唯一
static完全不管你能不能修改!
csharp
public static int Count = 0;
Count = 100; //✅随便改,static不锁读写
static只管「几份副本」,根本没有只读、不可修改的能力。
三、static + readonly 组合(就是你NtsHelper里面的写法)
csharp
private static readonly GeometryFactory _factory = new GeometryFactory();
两个关键字分工清晰,各司其职:
static→ 全局唯一,全程序只有这一个_factory对象,不需要new实例就能访问readonly→ 赋值锁定,只能初始化那一次,后面代码禁止修改 _factory
| 特性 | 由哪个关键字提供 |
|---|---|
| 全局唯一、一份副本 | static |
| 仅构造阶段可赋值一次 | readonly |
| 运行全程不能修改 | readonly |
分开对比:
static:管「有几份」readonly:管「能不能改、什么时候赋值」
四、再把 const 拉进来对比(你代码里还有 const double Tolerance)
const = 编译期常量,天生隐式static
csharp
private const double Tolerance = 1e-6;
- 编译的时候值就被钉死,不能在构造函数赋值;
- 天生全局唯一,不能加readonly。
一张终极对比总表
| 写法 | 副本数量(全局唯一?) | 什么时候能赋值 | 运行时能否修改 |
|---|---|---|---|
int x; |
实例:每个new对象一份 | 任何地方 | 可读写 |
readonly int x; |
实例:每个new对象一份 | 声明处 / 实例构造函数 | ❌不可修改 |
static int x; |
全局唯一1份 | 任何地方 | ✅随便读写 |
static readonly int x; |
全局唯一1份 | 声明处 / 静态构造函数 | ❌不可修改 |
const int x = 1; |
全局唯一1份 | 只能声明时赋值 | ❌编译期固定,不可修改 |
五、结合你NTS管网代码解释
csharp
private static readonly GeometryFactory _factory = new GeometryFactory();
static:GeometryFactory 工厂对象全局就创建1个,所有管线几何运算共用它;readonly:写完之后代码不能再把_factory = null或者_factory = new GeometryFactory(),防止运行时被意外覆盖,引发难以排查的NTS崩溃。
高频踩坑点
- ❌误区:
readonly= 全局唯一
错!不带static的readonly,每个实例一份。
- ❌误区:
static= 不能修改
错!static变量默认完全可读写。
- ❌误区:
static readonly和const完全等价
不等价:
static readonly可以在静态构造函数里面动态赋值,const只能写死字面量。