1.成员访问运算符和表达式
访问类型成员时,可以使用以下运算符和表达式:
- .(成员访问):用于访问命名空间或类型的成员
- [](数组元素或索引器访问):用于访问数组元素或类型索引器
- ?. 和 ?[](null 条件运算符):仅当操作数为非 null 时才用于执行成员或元素访问运算
- ()(调用):用于调用被访问的方法或调用委托
- ^(从末尾开始索引):指示元素位置来自序列的末尾
- ..(范围):指定可用于获取一系列序列元素的索引范围
2.成员访问表达式
可以使用 .
标记来访问命名空间或类型的成员,如以下示例所示:
- 使用
.
访问命名空间内的嵌套命名空间,如以下 using directive 的示例所示:
C#复制
using System.Collections.Generic;
- 使用
.
构成限定名称以访问命名空间中的类型,如下面的代码所示:System.Collections.Generic.IEnumerable<int> numbers = new int[] { 1, 2, 3 };
使用 using 指令来使用可选的限定名称。
- 使用
.
访问类型成员(静态和非静态),如下面的代码所示
还可以使用var constants = new List<double>(); constants.Add(Math.PI); constants.Add(Math.E); Console.WriteLine($"{constants.Count} values to show:"); Console.WriteLine(string.Join(", ", constants));
.
访问扩展方法。
3.索引器运算符 []
方括号 []
通常用于数组、索引器或指针元素访问。
4.数组访问
下面的示例演示如何访问数组元素:
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
如果数组索引超出数组相应维度的边界,将引发 IndexOutOfRangeException。
如上述示例所示,在声明数组类型或实例化数组实例时,还会使用方括号。
5.索引器访问
下面的示例使用 .NET Dictionary<TKey,TValue> 类型来演示索引器访问:
var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]); // output: 4.14159265358979
6.[]的其他用法
要了解指针元素访问,请参阅与指针相关的运算符一文的指针元素访问运算符 [] 部分。
方括号还用于指定属性:
[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}
7. Null 条件运算符 ?. 和 ?[]
Null 条件运算符在 C# 6 及更高版本中可用,仅当操作数的计算结果为非 null 时,null 条件运算符才会将成员访问 ?.
或元素访问 ?[]
运算应用于其操作数;否则,将返回 null
。 即:
-
如果
a
的计算结果为null
,则a?.x
或a?[x]
的结果为null
。 -
如果
a
的计算结果为非 null,则a?.x
或a?[x]
的结果将分别与a.x
或a[x]
的结果相同。
8.NULL 条件运算符采用最小化求值策略。 也就是说,如果条件成员或元素访问运算链中的一个运算返回 null
,则链的其余部分不会执行。 在以下示例中,如果 A
的计算结果为 null
,则不会计算 B
;如果 A
或 B
的计算结果为 null
,则不会计算 C
:
A?.B?.Do(C);
A?.B?[C];
9.如果 A
可以为 NULL,但如果 A 不为 NULL,B
和 C
将不为 NULL,你只需要对 A
应用 NULL 条件运算符:
A?.B.C();
10.在上述示例中,如果 A
为 null,则不会计算 B
,也不会调用 C()
。 但是,如果链接的成员访问被中断,例如被 (A?.B).C()
中的括号中断,则不会发生短路。
以下示例演示了 ?.
和 ?[]
运算符的用法:
double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
{
return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}
var sum1 = SumNumbers(null, 0);
Console.WriteLine(sum1); // output: NaN
var numberSets = new List<double[]>
{
new[] { 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
using System;
using System.Collections.Generic;
using System.Linq;
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 FullName Name { get; set; }
}
public class FullName
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void Write()
{
Console.WriteLine($"{FirstName} {LastName}");
}
}
}
前面两个示例中的第一个也使用 Null 合并运算符 ?? 来指定替代表达式,以便在 null 条件运算的结果为 null
时用于计算。
如果 a.x
或 a[x]
是不可为 null 的值类型 T
,则 a?.x
或 a?[x]
属于对应的可为 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(new int[0])); // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new[] { 3, 4, 5 })); // output: 7
在前面的示例中,如果不使用 ??
运算符,则在 numbers
为 null
时,numbers?.Length < 2
的计算结果为 false
。
Null 条件成员访问运算符 ?.
也称为 Elvis 运算符。
11.线程安全的委托调用
PropertyChanged?.Invoke(…)
该代码等同于将在 C# 5 或更低版本中使用的以下代码:
var handler = this.PropertyChanged;
if (handler != null)
{
handler(…);
}
12.这是一种线程安全方法,可确保只调用非 null handler
。 由于委托实例是不可变的,因此,任何线程都不能更改 handler
本地变量所引用的对象。 具体而言,如果另一个线程执行的代码从 PropertyChanged
事件中取消订阅,并且 PropertyChanged
在调用 handler
之前变为 null
,则 handler
引用的对象不受影响。 ?.
运算符对其左操作数的计算不超过一次,从而确保在验证为非 null 后,不能将其更改为 null
。
13.调用的表达式
以下示例演示如何在使用或不使用参数的情况下调用方法,以及调用委托:
Action<int> display = s => Console.WriteLine(s);
var numbers = new List<int>();
numbers.Add(10);
numbers.Add(17);
display(numbers.Count); // output: 2
numbers.Clear();
display(numbers.Count); // output: 0
13.() 的其他用法
此外可以使用括号来调整表达式中计算操作的顺序。 有关详细信息,请参阅 C# 运算符。
强制转换表达式,其执行显式类型转换,也可以使用括号。
14.从末尾运算符^开始索引
^
运算符在 C# 8.0 和更高版本中提供,指示序列末尾的元素位置。 对于长度为 length
的序列,^n
指向与序列开头偏移 length - n
的元素。 例如,^1
指向序列的最后一个元素,^length
指向序列的第一个元素。
int[] xs = new[] { 0, 10, 20, 30, 40 };
int last = xs[^1];
Console.WriteLine(last); // output: 40
var lines = new List<string> { "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
属于 System.Index 类型。 在表达式 ^e
中,e
的结果必须隐式转换为 int
。
还可以将 ^
运算符与范围运算符一起使用以创建一个索引范围。 有关详细信息,请参阅索引和范围。