考虑以下两种数据类型:
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 控制),就像ldloca
或ldflda
一样。当您在 C# 中使用ref
时,这正是它的作用。 - IS4