声明语句
“声明语句”声明一个新变量,并对其进行初始化(可选)。 所有变量都有已声明类型。 可以参阅有关 .NET 类型系统的文章,详细了解类型。
隐式类型本地变量
在方法范围内声明的变量可以具有隐式“类型”var
。 隐式类型本地变量为强类型,就像用户已经自行声明该类型,但编译器决定类型一样。 a
和 b
的以下两个声明在功能上是等效的:
var a = 10; // Implicitly typed.
int b = 10; // Explicitly typed.
重要
在启用可为空引用类型的情况下使用 var
时,即使表达式类型不可为空,也始终表示可为空引用类型。 编译器的 null 状态分析可防止取消引用潜在的 null
值。 如果从未向可能为 null 的表达式分配该变量,编译器将不会发出任何警告。 如果将该变量分配给可能为 null 的表达式,则必须先测试其是否为 null,然后才能将其取消引用,以避免出现任何警告。
var
关键字的常见用途是用于构造函数调用表达式。 使用 var
则不能在变量声明和对象实例化中重复类型名称,如下面的示例所示:
var xs = new List<int>();
从 C# 9.0 开始,可以使用由目标确定类型的 new
表达式作为替代方法:
List<int> xs = new();
List<int>? ys = new();
在模式匹配中,在 var
模式中使用 var
关键字。
下面的示例演示两个查询表达式。 在第一个表达式中,允许使用 var
,但不是必需的,因为查询结果的类型可以明确表述为 IEnumerable<string>
。 不过,在第二个表达式中,var
允许结果是一系列匿名类型,且相应类型的名称只可供编译器本身访问。 如果使用 var
,便无法为结果新建类。 在示例 #2 中,foreach
迭代变量 item
必须也为隐式类型。
// Example #1: var is optional when
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
where word[0] == 'g'
select word;
// Because each element in the sequence is a string,
// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
Console.WriteLine(s);
}
// Example #2: var is required because
// the select clause specifies an anonymous type
var custQuery = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
// var must be used because each item
// in the sequence is an anonymous type
foreach (var item in custQuery)
{
Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}
ref 局部变量
在变量类型前添加 ref
关键字以声明 ref
局部变量。 假设 GetContactInformation
方法声明为 ref 返回:
public ref Person GetContactInformation(string fname, string lname)
按值分配会读取变量值,并将它分配给新变量:
Person p = contacts.GetContactInformation("Brandie", "Best");
上面的分配将 p
声明为本地变量。 它的初始值是通过读取 GetContactInformation
返回的值进行复制。 之后对 p
的任何分配都不会更改 GetContactInformation
返回的变量值。 变量 p
不再是返回的变量的别名。
声明 ref 变量,复制原始值的别名。 在下面的分配中,p
是从 GetContactInformation
返回的变量的别名。
ref Person p = ref contacts.GetContactInformation("Brandie", "Best");
后续使用 p
等同于使用 GetContactInformation
返回的变量,因为 p
是此变量的别名。 对 p
所做的更改也会更改从 GetContactInformation
返回的变量。
可通过相同方式按引用访问值。 在某些情况下,按引用访问值可避免潜在的高开销复制操作,从而提高性能。 例如,以下语句显示用户可如何定义一个用于引用值的 ref 局部变量值。
ref VeryLargeStruct reflocal = ref veryLargeStruct;
ref
关键字用于局部变量声明前面和第二个示例中的值前面。 在这两个示例中,如果无法同时将 ref
关键字包含在变量声明和赋值中,则会导致编译器错误 CS8172:“无法使用值对按引用变量进行初始化”。
ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.
Ref 局部变量仍必须在声明时进行初始化。
下列示例定义存储整数值数组的 NumberStore
类。 FindNumber
方法按引用返回第一个大于或等于作为参数传递的数字的数字。 如果没有大于或等于该参数的数字,则方法返回索引 0 中的数字。
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
for (int ctr = 0; ctr < numbers.Length; ctr++)
{
if (numbers[ctr] >= target)
return ref numbers[ctr];
}
return ref numbers[0];
}
public override string ToString() => string.Join(" ", numbers);
}
下列示例调用 NumberStore.FindNumber
方法来检索大于或等于 16 的第一个值。 然后,调用方将该方法返回的值加倍。 示例输出表明,NumberStore
实例的数组元素值反映了更改。
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence: {store.ToString()}");
// The example displays the following output:
// Original sequence: 1 3 7 15 31 63 127 255 511 1023
// New sequence: 1 3 7 15 62 63 127 255 511 1023
如果引用返回值不受支持,需要通过返回数组元素及其值的索引来执行此类操作。 然后,调用方可使用此索引修改单个方法调用中的值。 但调用方也可修改要访问的索引,还可修改其他数组值。
下面的示例展示了如何将 FindNumber
方法重写为使用 ref 局部重新分配:
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
ref int returnVal = ref numbers[0];
var ctr = numbers.Length - 1;
while ((ctr >= 0) && (numbers[ctr] >= target))
{
returnVal = ref numbers[ctr];
ctr--;
}
return ref returnVal;
}
public override string ToString() => string.Join(" ", numbers);
}
如果查找的数字更接近数组末尾,则第二个版本使用较长的序列会更有效,因为数组是从末尾向开头迭代,使需要检查的项更少。
编译器对 ref
变量强制实施范围规则:ref
局部变量、ref
参数和 ref struct
类型中的 ref
字段。 这些规则可确保引用在时间上不超过它所引用的对象。 请参阅关于方法参数的文章中有关范围界定规则的部分。
ref 和 readonly
readonly
修饰符可以应用于 ref
局部变量和 ref
字段。 readonly
修饰符会影响其右边的表达式。 请参阅以下示例声明:
ref readonly int aConstant; // aConstant can't be value-reassigned.
readonly ref int Storage; // Storage can't be ref-reassigned.
readonly ref readonly int CantChange; // CantChange can't be value-reassigned or ref-reassigned.
- “重新赋值”表示重新分配变量的值。
- “ref 赋值”表示变量现在引用其他对象。
readonly ref
和 readonly ref readonly
声明仅对 ref struct
中的 ref
字段有效。
scoped ref
上下文关键字 scoped
限制值的生存期。 scoped
修饰符将 ref-safe-to-escape 或 safe-to-escape 生存期分别限制为当前方法。 实际上,添加 scoped
修饰符可确保代码不会延长变量的生存期。
可以将 scoped
应用于参数或局部变量。 当类型为 ref struct
时,scoped
修饰符可以应用于参数和局部变量。 否则,scoped
修饰符只能应用于 ref 类型的局部变量。 这包括使用 ref
修饰符声明的局部变量以及使用 in
、ref
或 out
修饰符声明的参数。
当类型为 ref struct
时,使用 struct
、out
参数和 ref
参数声明的方法将 scoped
修饰符隐式添加到 this
。