C#语言参考:数组访问及下标越界引发异常的相关说明

使用多个运算符和表达式来访问类型成员。 成员访问运算符包括成员访问(.)、数组元素或索引器访问()、从端索引(^)、范围(..)、null 条件运算符(?. 和 ?)和方法调用(())。 这些运算符包括 空条件 成员访问(?.)和索引器访问(?)运算符。

C# 语言参考记录了 C# 语言的最新发布版本。 它还包含即将发布的语言版本公共预览版中功能的初始文档。

本文档标识了在语言的最后三个版本或当前公共预览版中首次引入的任何功能。

小窍门

若要查找 C# 中首次引入功能时,请参阅 有关 C# 语言版本历史记录的文章。

成员访问表达式 .

.使用令牌访问命名空间或类型的成员,如以下示例所示:

using System.Collections.Generic;

System.Collections.Generic.IEnumerable numbers = [1, 2, 3];

使用 using 指令来使用可选的限定名称。

List constants =
[
    Math.PI,
    Math.E
];
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905

还可以用于 . 访问 扩展成员。

索引器运算符

方括号( 通常访问数组、索引器或指针元素)。 从 C# 12 开始, 包含一个集合表达式。

数组访问

下面的示例演示如何访问数组元素:

int[] fib = new int[10];
fib[0] = fib[1] = 1;
for (int i = 2; i < fib.Length; i++)
{
    fib[i] = fib[i - 1] + fib[i - 2];
}
Console.WriteLine(fib[fib.Length - 1]);  // output: 55
double[,] matrix = new double[2,2];
matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
matrix[1,0] = matrix[1,1] = 3.0;
var determinant = matrix[0,0] * matrix[1,1] - matrix[1,0] * matrix[0,1];
Console.WriteLine(determinant);  // output: -3

如果数组索引超出数组的相应维度的边界,运行时将引发一个 tion。

如上述示例所示,在声明数组类型或实例化数组实例时,还会使用方括号。

有关数组的详细信息,请参阅数组。

索引器访问

下面的示例使用 .NET 类型来演示索引器访问:

var dict = new Dictionary();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]);  // output: 4.14159265358979

索引器允许你以与数组索引类似的方式为用户定义的类型的实例编制索引。 与必须是整数的数组索引不同,可以将索引器参数声明为任何类型。

有关索引器的详细信息,请参阅索引器。

的其他用法

要了解指针元素访问,请参阅一文的指针元素访问运算符 部分。 有关集合表达式的信息,请参阅集合表达式一文。

方括号还用于指定属性:

[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}

此外,使用方括号指定用于模式匹配或测试 的列表模式 。

arr is ([1, 2, ..])
//Specifies that an array starts with (1, 2)

Null 条件运算符 ?. 和 ?

仅当作数的计算结果为非 null 时,null 条件运算符才会将 (?.)或 (?)作应用于其作数。 否则,它将返回 null。 换句话说:

NULL 条件运算符采用最小化求值策略。 也就是说,如果条件成员或元素访问操作链中的一个操作返回null,则链的其余部分不会执行。 在以下示例中,如果 B 的计算结果为 A,则不会计算 null;如果 C 或 A 的计算结果为 B,则不会计算 null:

A?.B?.Do(C);
A?.B?[C];

如果 A 可以为 null,而如果 A 不为 null,B 和 C 将不为 null,你只需要对 A 应用 null 条件运算符。

A?.B.C();

在上述示例中,如果 B 为 null,则不会计算 C(),也不会调用 A。 但是,如果链接的成员访问被中断,例如被 (A?.B).C() 中的括号中断,则不会发生短路。

以下示例演示了 ?. 和 ? 运算符的用法:

double SumNumbers(List setsOfNumbers, int indexOfSetToSum)
{
    return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}
var sum1 = SumNumbers(null, 0);
Console.WriteLine(sum1);  // output: NaN
List numberSets =
[
    [1.0, 2.0, 3.0],
    null
];
var sum2 = SumNumbers(numberSets, 0);
Console.WriteLine(sum2);  // output: 6
var sum3 = SumNumbers(numberSets, 1);
Console.WriteLine(sum3);  // output: NaN

namespace MemberAccessOperators2;
public static class NullConditionalShortCircuiting
{
    public static void Main()
    {
        Person? person = null;
        person?.Name.Write(); // no output: Write() is not called due to short-circuit.
        try
        {
            (person?.Name).Write();
        }
        catch (NullReferenceException)
        {
            Console.WriteLine("NullReferenceException");
        }; // output: NullReferenceException
    }
}
public class Person
{
    public required FullName Name { get; set; }
}
public class FullName
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public void Write() => Console.WriteLine($"{FirstName} {LastName}");
}

前面的第一个示例还使用 null 合并运算符 ?? 来指定一个替代表达式来计算 null 条件运算 null的结果。

如果 a.x 或 a 是不可为 null 的值类型 T,则 a?.x 或 a? 属于对应的 可为 null 的值类型T?。 如果需要 T 类型的表达式,请将 Null 合并操作符 ?? 应用于 null 条件表达式,如下面的示例所示:

int GetSumOfFirstTwoOrDefault(int[]? numbers)
{
    if ((numbers?.Length ?? 0) < 2)
    {
        return 0;
    }
    return numbers[0] + numbers[1];
}
Console.WriteLine(GetSumOfFirstTwoOrDefault(null));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([3, 4, 5]));  // output: 7

在前面的示例中,如果不使用 ?? 运算符,则在 ?. < 2 为 false 时, 的计算结果为 null。

注意

运算符 ?. 计算其左侧作数不超过一次,以确保在验证为非 null 后无法将其更改为 null 。

从 C# 14 开始,允许对引用类型使用 null 条件访问表达式(?. 和 ?)分配。 例如,请参阅以下方法:

person?.FirstName = "Scott";
messages?[5] = "five";

前面的示例显示了对可能为 null 的引用类型的属性和索引元素的赋值。 该赋值的一个重要行为是,只有当已知左侧表达式为非空时,才会对 = 右侧的表达式进行求值。 例如,在以下代码中,仅当数组不为 null 时,才会调用该函数。 如果数组为 null, 则不调用:

values?[2] = GenerateNextIndex();
int GenerateNextIndex() => index++;

换句话说,前面的代码等效于下面使用 if 语句进行 null 检查的代码:

if (values is not null)
{
    values[2] = GenerateNextIndex();
}

除赋值外,允许使用任何形式的 ,例如 += 或 -=。 但是,不允许递增(++)和递减(–)。

此增强功能不会将 null 条件表达式分类为变量。 它不能 ref 分配,也不能将其分配给 ref 变量,也不能作为 ref 或 out 参数传递给方法。

线程安全的委托调用

?.使用运算符检查委托是否不为 null,并采用线程安全的方式调用它(例如,引发事件时),如以下代码所示:

PropertyChanged?.Invoke(…)

该代码等同于以下代码:

var handler = this.PropertyChanged;
if (handler != null)
{
    handler(…);
}

前面的示例是一种线程安全方法,可确保只调用非 null 。 由于委托实例是不可变的,因此,任何线程都不能更改 本地变量所引用的对象。 具体而言,如果另一个线程执行的代码从 事件中取消订阅,并且 在调用 null 之前变为 ,则 引用的对象不受影响。

调用表达式 ()

使用括号 () 调用方法或调用委托。

以下代码演示了如何在使用或不使用参数的情况下调用方法,以及调用委托:

Action display = s => Console.WriteLine(s);
List numbers =
[
    10,
    17
];
display(numbers.Count);   // output: 2
numbers.Clear();
display(numbers.Count);   // output: 0

使用运算符调用 构造函数 时,还可以使用 new 括号。

() 的其他用法

此外可以使用括号来调整表达式中计算操作的顺序。 有关更多信息,请参见 C# 运算符。

,其执行显式类型转换,也可以使用括号。

从末尾运算符 ^ 开始索引

可以将索引和范围运算符与 可计数的类型一起使用。可计数类型具有一个名为或具有可访问访问get器的属性。 集合表达式也依赖于可数类型。

注意

单维数组 可计数。 多维数组不是。 不能在多维数组中使用 ^ 和 .. (range) 运算符。

^ 运算符指示序列末尾的元素位置。 对于长度为 的序列,^n 指向从序列起始偏移 – n 的元素。 例如,^1 指向序列的最后一个元素,^ 指向序列的第一个元素。

int[] xs = [0, 10, 20, 30, 40];
int last = xs[^1];
Console.WriteLine(last);  // output: 40
List lines = ["one", "two", "three", "four"];
string prelast = lines[^2];
Console.WriteLine(prelast);  // output: three
string word = "Twenty";
Index toFirst = ^word.Length;
char first = word[toFirst];
Console.WriteLine(first);  // output: T

如前面的示例所示,表达式 ^e 是 .Index 类型。 在表达式 ^e 中,e 的结果必须能够隐式转换为 int。

还可以将^运算符与一起使用,以创建一个索引范围。 有关详细信息,请参阅索引和范围。

从 C# 13 开始,可以在对象初始值设定项中使用 end 运算符 ^中的 Index。

范围运算符 ..

.. 运算符指定索引范围的开始和结束作为其操作数。 左侧操作数是范围的包含性开头。 右侧操作数是范围的包含性末尾。 作数可以是序列开头或末尾的索引,如以下示例所示:

int[] numbers = [0, 10, 20, 30, 40, 50];
int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
Display(subset);  // output: 10 20 30
int margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner);  // output: 10 20 30 40
string line = "one two three";
int amountToTakeFromEnd = 5;
Range endIndices = ^amountToTakeFromEnd..^0;
string end = line[endIndices];
Console.WriteLine(end);  // output: three
void Display(IEnumerable xs) => Console.WriteLine(string.Join(" ", xs));

如前面的示例所示,表达式 a..b 是 .Range 类型。 在表达式 a..b 中,a 和 b 的结果必须能隐式转换为 Int32 或 Index。

重要

当值为负数时,从 int 隐式转换为 Index 会引发 。

通过省略 .. 运算符的任何操作数,可以获得一个开放区间:

int[] numbers = [0, 10, 20, 30, 40, 50];
int amountToDrop = numbers.Length / 2;
int[] rightHalf = numbers[amountToDrop..];
Display(rightHalf);  // output: 30 40 50
int[] leftHalf = numbers[..^amountToDrop];
Display(leftHalf);  // output: 0 10 20
int[] all = numbers[..];
Display(all);  // output: 0 10 20 30 40 50
void Display(IEnumerable xs) => Console.WriteLine(string.Join(" ", xs));

下表显示了表达集合范围的各种方法:

范围运算符表达式说明

..

集合中的所有值。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注