声明语句
声明语句声明新的局部变量、局部常量或 reference 局部变量。 若要声明局部变量,请指定其类型并提供其名称。 可以在一个语句中声明多个相同类型的变量,如以下示例所示:
string greeting;
int a, b, c;
List<double> xs;
在声明语句中,还可以使用变量的初始值对该变量进行初始化:
string greeting = "Hello";
int a = 3, b = 2, c = a + b;
List<double> xs = new();
前面的示例显式指定了变量的类型。 还可以让编译器从其初始化表达式推断出变量的类型。 为此,请使用 var
关键字而不是类型的名称。 有关详细信息,请参阅隐式类型的局部变量部分。
若要声明局部常量,请使用 const
关键字,如以下示例所示:
const string Greeting = "Hello";
const double MinLimit = -10.0, MaxLimit = -MinLimit;
声明局部常量时,还必须对其进行初始化。
有关 reference 局部变量的信息,请参阅 Reference 变量部分。
隐式类型的局部变量
声明局部变量时,可以让编译器从初始化表达式推断出变量的类型。 为此,请使用 var
关键字而不是类型的名称:
var greeting = "Hello";
Console.WriteLine(greeting.GetType()); // output: System.String
var a = 32;
Console.WriteLine(a.GetType()); // output: System.Int32
var xs = new List<double>();
Console.WriteLine(xs.GetType()); // output: System.Collections.Generic.List`1[System.Double]
如前面的示例所示,隐式类型的局部变量已强类型化。
注意
在已启用的可为 null 的感知上下文中使用 var
,且初始化表达式的类型为引用类型时,即使初始化表达式的类型不可为 null,编译器也始终推断出可为 null 的引用类型。
var
的常见用途是用于构造函数调用表达式。 使用 var
则不能在变量声明和对象实例化中重复类型名称,如下面的示例所示:
var xs = new List<int>();
可以使用由目标确定类型的 new
表达式作为替代方法:
List<int> xs = new();
List<int>? ys = new();
使用匿名类型时,必须使用隐式类型的局部变量。 以下示例显示了一个查询表达式,该表达式使用匿名类型保存客户的姓名和电话号码:
var fromPhoenix = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
foreach (var customer in fromPhoenix)
{
Console.WriteLine($"Name={customer.Name}, Phone={customer.Phone}");
}
在前面的示例中,无法显式指定 fromPhoenix
变量的类型。 类型为 IEnumerable<T>,但在本例中 T
为匿名类型,无法提供其名称。 这就是需要使用 var
的原因。 出于同一原因,在 foreach
语句中声明 customer
迭代变量时必须使用 var
。
有关隐式类型的局部变量的详细信息,请参阅隐式类型的局部变量。
在模式匹配中,在 var
模式中使用 var
关键字。
Reference 变量
声明局部变量并在变量类型之前添加 ref
关键字时,声明 reference 变量,或 ref
局部变量:
ref int aliasOfvariable = ref variable;
reference 变量是引用另一个变量(称为引用)的变量。 也就是说,reference 变量是其引用的别名。 向 reference 变量赋值时,该值将分配给引用。 读取 reference 变量的值时,将返回引用的值。 以下示例演示了该行为:
int a = 1;
ref int aliasOfa = ref a;
Console.WriteLine($"(a, aliasOfa) is ({a}, {aliasOfa})"); // output: (a, aliasOfa) is (1, 1)
a = 2;
Console.WriteLine($"(a, aliasOfa) is ({a}, {aliasOfa})"); // output: (a, aliasOfa) is (2, 2)
aliasOfa = 3;
Console.WriteLine($"(a, aliasOfa) is ({a}, {aliasOfa})"); // output: (a, aliasOfa) is (3, 3)
使用 ref
赋值运算符 = ref
更改 reference 变量的引用,如以下示例所示:
void Display(int[] s) => Console.WriteLine(string.Join(" ", s));
int[] xs = [0, 0, 0];
Display(xs);
ref int element = ref xs[0];
element = 1;
Display(xs);
element = ref xs[^1];
element = 3;
Display(xs);
// Output:
// 0 0 0
// 1 0 0
// 1 0 3
在前面的示例中,element
reference 变量初始化为第一个数组元素的别名。 然后,ref
将被重新分配,以引用最后一个数组元素。
可以定义 ref readonly
局部变量。 不能为 ref readonly
变量赋值。 但是,可以 ref
重新分配这样的 reference 变量,如以下示例所示:
int[] xs = [1, 2, 3];
ref readonly int element = ref xs[0];
// element = 100; error CS0131: The left-hand side of an assignment must be a variable, property or indexer
Console.WriteLine(element); // output: 1
element = ref xs[^1];
Console.WriteLine(element); // output: 3
可以将引用返回分配给 reference 变量,如以下示例所示:
using System;
public class NumberStore
{
private readonly int[] numbers = [1, 30, 7, 1557, 381, 63, 1027, 2550, 511, 1023];
public ref int GetReferenceToMax()
{
ref int max = ref numbers[0];
for (int i = 1; i < numbers.Length; i++)
{
if (numbers[i] > max)
{
max = ref numbers[i];
}
}
return ref max;
}
public override string ToString() => string.Join(" ", numbers);
}
public static class ReferenceReturnExample
{
public static void Run()
{
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
ref int max = ref store.GetReferenceToMax();
max = 0;
Console.WriteLine($"Updated sequence: {store.ToString()}");
// Output:
// Original sequence: 1 30 7 1557 381 63 1027 2550 511 1023
// Updated sequence: 1 30 7 1557 381 63 1027 0 511 1023
}
}
在前面的示例中,GetReferenceToMax
方法指的是 returns-by-ref 方法。 它不返回最大值本身,而是引用返回,该引用返回是包含最大值的数组元素的别名。 Run
方法将引用返回分配给 max
reference 变量。 然后,通过分配给 max
,它会更新 store
实例的内部存储。 还可以定义 ref readonly
方法。 ref readonly
方法的调用方无法为其引用返回赋值。
foreach
语句的迭代变量可以是引用变量。 有关详细信息,请参阅迭代语句一文的 foreach
语句部分。
在性能关键型方案中,使用 reference 变量和返回可能会避免成本高昂的复制操作,从而提高性能。
编译器确保 reference 变量在时间上不超过其引用,并在其整个生存期内保持有效。 有关详细信息,请参阅 C# 语言规范中的 Ref safe 上下文部分。
有关 ref
字段的信息,请参阅 ref
结构类型一文的 ref
字段部分。
scoped ref
上下文关键字 scoped
限制值的生存期。 scoped
修饰符将 ref-safe-to-escape 或 safe-to-escape 生存期分别限制为当前方法。 实际上,添加 scoped
修饰符可确保代码不会延长变量的生存期。
可以将 scoped
应用于参数或局部变量。 当类型为 ref struct
时,scoped
修饰符可以应用于参数和局部变量。 否则,scoped
修饰符只能应用于局部 reference 变量。 这包括使用 ref
修饰符声明的局部变量以及使用 in
、ref
或 out
修饰符声明的参数。
当类型为 ref struct
时,使用 struct
、out
参数和 ref
参数声明的方法将 scoped
修饰符隐式添加到 this
。
C# 语言规范
有关更多信息,请参阅 C# 语言规范的以下部分:
有关 scoped
修饰符的详细信息,请参阅低级别 struct 改进建议说明。