变量是一个有名字、类型固定的存储位置。在 C# 中,你用类型和名字来声明变量,也可以在同一条语句里直接给它赋值。之后类型永远不会改变:一个 int 变量在它存在期间只能存放整数。
输出:
Priya bought 5 items for 20.25
10
赋给变量错误类型的值会导致编译错误:quantity = "five"; 会报 CS0029(cannot implicitly convert type 'string' to 'int')。类型之间的转换必须显式进行,见类型转换。
var:让编译器推断类型
var 声明一个局部变量,它的类型由初始化表达式推断得出。这不是动态类型:类型在编译时就已经确定,和你亲手写出类型完全一样。
输出:
String
Int32
Decimal
Double
List`1
var 有三条规则。它只能用于局部变量(不能用于字段或参数)。变量必须在声明时初始化:var x; 会报错误 CS0818,“Implicitly-typed variables must be initialized”。而且初始化表达式必须有类型:var nothing = null; 无法编译,因为单独的 null 没有类型。
什么时候用它取决于团队风格。通常的做法是:类型从右侧一眼就能看出来时用 var,看不出来时就写出类型:
var orders = new List<Order>(); // obvious: var is fine
var reader = new StreamReader(path); // obvious
decimal discount = CalculateDiscount(); // not obvious: name the type
int count = items.Count(); // makes the intent clear
一个不易察觉的陷阱:var total = 0; 会让 total 成为 int,所以之后给它加上 2.5 就会出错;var price = 10; 也是 int,而不是 decimal。当字面量的类型不是你想要的类型时,就写出类型或者加上后缀(10m、0.0)。
作用域
局部变量从声明处开始存在,直到声明它的代码块 { } 结束为止。在循环头部或 if 语句体中声明的变量,在它们之后就不可见了。
输出:
high
240
与 C 和 C++ 不同,C# 不允许内层代码块声明与外层代码块中局部变量同名的变量(错误 CS0136):一个名字在整个方法中只能代表一个东西。两个并列的代码块可以复用同一个名字,比如两个独立的 for 循环都可以各自声明 i。
明确赋值
局部变量没有默认值。编译器会追踪方法中的每一条执行路径,只要某个变量可能在赋值之前被读取,就拒绝编译:
int discount;
if (isMember)
{
discount = 10;
}
Console.WriteLine(discount);
// error CS0165: Use of unassigned local variable 'discount'
解决方法是在声明时初始化(int discount = 0;),或者在每个分支中都赋值(加上 else)。字段和数组元素则不同:它们会被自动设为其类型的默认值(0、false、null)。
const:编译时常量
const 是在编译时就确定的值。它必须用常量表达式初始化(字面量、另一个 const,或者对它们进行的运算),而且只能是数字、char、bool、string、枚举或 null。
输出:
Price in EUR
120.00 EUR
350
const 字段隐式为 static,所以要通过类型名访问(Pricing.VatRate),而写成 static const 会报错。编译器会把这个值复制到每一个使用它的地方,所以 const 在运行时没有任何开销;这也是为什么库中公开的 const 应该是一个永远不会变的值(比如一周的天数)。
readonly 和 static readonly
readonly 字段只能在声明处或所在类的构造函数中赋值。对象构造完成之后,它就不能再改变。和 const 不同,它的值在运行时计算,可以是任何类型,不同对象的值也可以不同。
输出:
ACC-17 opened 2024-05-02
Launch: 2020-01-15
basic, pro
最后几行展示了一个常见的误解:readonly 保护的是字段,而不是字段指向的对象。Plans 会一直指向同一个数组,但数组的元素仍然可以修改。如果需要真正不可变的集合,请使用 ReadOnlyCollection<T> 或 ImmutableArray<T>。
三者对比:
const | readonly | static readonly | |
|---|---|---|---|
| 值何时确定 | 编译时 | 运行时 | 运行时 |
| 允许的类型 | 数字、char、bool、string、枚举、null | 任意 | 任意 |
| 在哪里赋值 | 只能在声明处 | 声明处或构造函数 | 声明处或静态构造函数 |
| 每个对象一份还是共享 | 共享(隐式 static) | 每个对象一份 | 共享 |
| 局部变量 | 可以(const int x = 1;) | 不可以 | 不可以 |
命名规范
C# 的名字区分大小写(total 和 Total 是两个不同的变量),并且必须以字母或下划线开头。下面是 Microsoft 的规范,几乎所有 C# 代码都遵循它:
- 局部变量和参数:camelCase(
orderCount、unitPrice)。 - 常量、属性、方法、类和公共字段:PascalCase(
MaxItems、VatRate)。 - 私有字段:以下划线开头的 _camelCase(
_balance),这是 .NET 代码中广泛使用的约定。 - 布尔变量读起来像一个问题:
isActive、hasDiscount、canRetry。 - 避免缩写和匈牙利命名前缀(
strName、iCount)。
C# 关键字不能用作名字,除非在前面加上 @:var @class = "5B"; 是合法的,这在生成的代码或 JSON 字段名中偶尔会用到。
交换与多重赋值
从 C# 7 开始,元组让你可以在一条语句中给多个变量赋值,这样交换两个变量只需要一行:
输出:
20 10
Lisbon: 545,000
第二种写法是解构:它从一个元组中声明了 string 类型的 city 和 int 类型的 population。更多内容见元组。
常见问题
C# 中的 var 是什么?
var 让编译器根据初始化的值推断局部变量的类型:var count = 5; 声明的是一个 int。这个变量依然是静态类型的,之后不能再存放字符串。var 只能用于在同一条语句中完成初始化的局部变量。
C# 中 const 和 readonly 有什么区别?
const 是编译时常量:它的值在编译时就必须确定(数字、字符串、bool、null),它隐式为 static,而且这个值会被复制到每一个使用它的地方。readonly 字段在运行时只赋值一次,在声明处或构造函数中赋值,可以是任何类型,每个对象的值也可以不同。
什么时候应该用 static readonly 而不是 const?
当一个共享的值无法成为编译时常量时(比如 DateTime、数组或对象),或者一个公开的值可能在库的不同版本之间变化时,就用 static readonly。因为 const 的值会被复制到调用它的程序集中,修改一个公开的 const 之后,所有使用它的程序集都必须重新编译。
C# 中的“use of unassigned local variable”是什么意思?
错误 CS0165:编译器发现存在一条路径,在局部变量被赋值之前就读取了它。C# 中的局部变量没有默认值。要么在声明时就初始化,要么确保 if/switch 的每个分支在使用前都给它赋了值。
C# 中应该用 var 还是显式类型?
两种都可以,由团队决定风格。常见的规则是:类型从右侧一眼就能看出来时用 var(var list = new List<string>();),看不出来时就写出类型(decimal total = GetTotal();)。.NET 运行时的代码库大致遵循这条规则。