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

9
考虑以下两种数据类型:
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 无法修改 'List<UserQuery.S>.this[int]' 的返回值,因为它不是一个变量。这很正常,因为实际上我们尝试修改的是 S 的临时副本,在给定的上下文中是 r-value。

但让我们试着对一个数组做同样的事情:

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)

And.. this works.

But

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数组和不安全指针时是可以预期的。但对于安全上下文来说,这有点出乎意料。为什么数组有一个特殊的不明显情况呢?为什么没有办法实现同样的行为来访问其他类型的成员?


为什么您认为在安全上下文中使用 ldelema 是意外的呢?它加载了托管引用(因此受 GC 控制),就像 ldlocaldflda 一样。当您在 C# 中使用 ref 时,这正是它的作用。 - IS4
2个回答

10
一个数组访问表达式被归类为变量。你可以对它进行赋值,通过引用传递等操作。一个索引器访问被独立地归类...在分类列表中(C# 5规范第7.1节)。
每个索引器访问都有一个关联类型,即索引器的元素类型。此外,一个索引器访问还有一个关联的实例表达式和一个关联的参数列表。当索引器访问的访问器(get或set块)被调用时,评估实例表达式的结果成为由this表示的实例(§7.6.7),并且评估参数列表的结果成为调用的参数列表。
可以将其视为字段和属性之间的区别:
 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

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

请注意,如果您一开始没有使用可变结构体,您不会以这种方式看到差异——我强烈建议不要使用可变结构体。


1
< p>像 List<T> 中的类索引器实际上是一种在语法上方便调用方法的方式。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接