声明语句

声明语句声明新的局部变量、局部常量或 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 alias = ref variable;

reference 变量是引用另一个变量(称为引用)的变量。 也就是说,reference 变量是其引用的别名。 向 reference 变量赋值时,该值将分配给引用。 读取 reference 变量的值时,将返回引用的值。 以下示例演示了该行为:

int a = 1;
ref int alias = ref a;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) is (1, 1)

a = 2;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) is (2, 2)

alias = 3;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) 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 修饰符声明的局部变量以及使用 inrefout 修饰符声明的参数。

当类型为 ref struct 时,使用 structout 参数和 ref 参数声明的方法将 scoped 修饰符隐式添加到 this

C# 语言规范

有关更多信息,请参阅 C# 语言规范的以下部分:

有关 scoped 修饰符的详细信息,请参阅低级别 struct 改进建议说明。

另请参阅