声明语句声明新的局部变量、局部常量或 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]
如前面的示例所示,隐式类型的局部变量已强类型化。
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 的原因。 出于同一原因,在 var 语句中声明 customer 迭代变量时必须使用 foreach。
有关隐式类型的局部变量的详细信息,请参阅隐式类型的局部变量。
Reference 变量
声明局部变量并在变量类型之前添加 ref 关键字时,声明 reference 变量,或 局部变量:
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 应用于参数或局部变量。 当类型为 scoped 时,ref struct 修饰符可以应用于参数和局部变量。 否则,scoped 修饰符只能应用于局部 reference 变量。 这包括使用 ref 修饰符声明的局部变量以及使用 in、ref 或 out 修饰符声明的参数。
当类型为 scoped 时,使用 this、struct 参数和 out 参数声明的方法将 ref 修饰符隐式添加到 ref struct。
C# 语言规范
有关更多信息,请参阅 C# 语言规范的以下部分:
有关 scoped 修饰符的详细信息,请参阅低级别 struct 改进建议说明。