数组索引器和任何其他对象索引器有什么区别

What the difference between array indexer and any other object indexer

考虑以下两种数据类型:

class C
{
    public int I { get; set; }
}

struct S
{
    public int I { get; set; }
}

让我们尝试在列表中使用它们,例如:

var c_list = new List<C> { new C { I = 1 } };
c_list[0].I++;

var s_list = new List<S> { new S { I = 1 } };
s_list[0].I++; // (a) CS1612 compilation error

不出所料,第 (a) 行出现编译错误:CS1612 Cannot modify the return value of 'List<UserQuery.S>.this[int]' because it is not a variable。这很好,因为实际上我们试图修改 S 的临时副本,这是给定上下文的 r 值。

但是让我们尝试对数组做同样的事情:

var c_arr = new[] { new C { I = 1 } };
c_arr[0].I++;

var s_arr = new[] { new S { I = 1 } };
s_arr[0].I++; // (b)

而且..这有效。

但是

var s_arr_list = (IList<S>) s_arr;
s_arr_list[0].I++;

不会像预期的那样编译。

如果我们查看生成的 IL,我们会发现以下内容:

IL_0057:  ldloc.1     // s_arr
IL_0058:  ldc.i4.0    // index
IL_0059:  ldelema     UserQuery.S // manager pointer of element

ldelema 将数组元素的地址加载到计算堆栈的顶部。 fixed 数组和不安全指针会出现这种行为。但是对于安全上下文来说,这有点出乎意料。为什么数组有一个特殊的不明显的情况?为什么没有为其他类型的成员实现相同行为的选项?

数组访问表达式被归类为变量。您可以分配给它,通过引用传递它等。索引器访问单独分类...在分类列表中(C# 5 规范第 7.1 节)

  • An indexer access. Every indexer access has an associated type, namely the element type of the indexer. Furthermore, an indexer access has an associated instance expression and an associated argument list. When an accessor (the get or set block) of an indexer access is invoked, the result of evaluating the instance expression becomes the instance represented by this (§7.6.7), and the result of evaluating the argument list becomes the parameter list of the invocation.

将此视为类似于字段与字段之间的区别 属性:

 public class Test
 {
     public int PublicField;
     public int PublicProperty { get; set; }
 }

 ...

 public void MethodCall(ref int x) { ... }

 ...

 Test test = new Test();
 MethodCall(ref test.PublicField); // Fine
 MethodCall(ref test.PublicProperty); // Not fine

从根本上说,索引器是一对方法(或单个方法),而数组访问为您提供一个存储位置。

请注意,如果您一开始没有使用可变结构,您将看不出这种方式的区别 - 我强烈建议您完全不要使用可变结构。

List<T> 中那样的 class 索引器实际上是一种调用方法的语法上方便的方式。

然而,对于数组,您实际上是在访问内存中的结构。在那种情况下没有方法调用。