考虑以下两种数据类型:
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
数组和不安全指针需要这种行为。但是对于安全的环境,这有点出乎意料。为什么阵列有一个特殊的不明显的例子?为什么没有选择为其他类型的成员实现相同的行为?
答案 0 :(得分:9)
数组访问表达式被归类为变量。您可以分配给它,通过引用传递等。索引器访问在分类列表中单独分类...(C#5规范第7.1节)。
- 索引器访问权限。每个索引器访问都有一个关联的类型,即索引器的元素类型。此外,索引器访问具有关联的实例表达式和关联的参数列表。当调用索引器访问的访问器(get或set块)时,评估实例表达式的结果将成为由此表示的实例(第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 :(得分:1)
像List<T>
中的类索引器实际上是一种语法上方便的方法来调用方法。
但是对于数组,您实际上是在访问内存中的结构。在这种情况下没有方法调用。