在 reza Is it possible to access a reference of a struct from a List to make changes?线程之后,我脑海中浮现出一个问题。
因此,请考虑以下struct
和interface
(定义不是很有用,只是为了显示问题):
public interface IChangeStruct
{
int Value { get; }
void Change(int value);
}
public struct MyStruct : IChangeStruct
{
int value;
public MyStruct(int _value)
{
value = _value;
}
public int Value
{
get
{
return value;
}
}
public void Change(int value)
{
this.value = value;
}
}
MyStruct
实现了IChangeStruct
,因此我们可以在堆中更改它的盒装副本,而不会取消装箱并替换为新的装箱。这可以通过以下代码演示:
MyStruct[] l1 = new MyStruct[]
{
new MyStruct(0)
};
Console.WriteLine(l1[0].Value); //0
l1[0].Change(10);
Console.WriteLine(l1[0].Value); //10
现在,让我们将数组更改为List<T>
,即:
List<MyStruct> l2 = new List<MyStruct>
{
new MyStruct(0)
};
Console.WriteLine(l2[0].Value); //0
l2[0].Change(10);
Console.WriteLine(l2[0].Value); //also 0
据我了解,在第一种情况下,l1[0]
将参考文献返回到盒装结构中,而在第二种情况下 - 它是其他结果。
我也试图反汇编这个并发现:
1)对于MyStruct[]
:
IL_0030: ldelema Utils.MyStruct IL_0035: ldc.i4.s 10 IL_0037: call instance void Utils.MyStruct::Change(int32)
2)对于List<MyStruct>
:
IL_007c: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<valuetype Utils.MyStruct>::get_Item(int32)
IL_0081: stloc.s CS$0$0001
IL_0083: ldloca.s CS$0$0001
IL_0085: ldc.i4.s 10
IL_0087: call instance void Utils.MyStruct::Change(int32)
但我似乎还没准备好解释它。
那么,List<T>
又回来了什么?或者数组和List<T>
如何通过索引返回元素?或者这只是值类型的情况,与引用类型无关?
PS:我做了解一个一定不能更改值类型实例,但所描述的问题让我理解,我从未意识到List<T>
和阵列工作。
答案 0 :(得分:9)
.Net可以使用ldelema
指令(数组元素的加载地址)就地寻址数组元素。
这使您可以直接在数组元素上操作而无需复制它们。 (这也是您可以将数组元素作为ref
或out
参数传递的原因)
List<T>
没有这样的能力。相反,list[i]
只是list.get_Item(i)
的语法糖,这是一个返回结构副本的普通方法调用。
答案 1 :(得分:3)
数组的索引器以类似于将其作为ref
参数传递的方式使元素可用于以下代码。任何其他类型的任何.net语言都不存在同样表现的机制。允许索引访问的任何其他类型必须公开一对方法,其中一个方法将内部存储数据的副本提供给调用者的代码,其中一个方法将给出来自调用者代码的一些数据的副本,存储那些数据以某种方式。这种限制在值类型中最明显,但在某些情况下也可能对引用类型有问题(例如,可以对Interlocked.ComapreExchange
中的元素执行T[]
,但不能对具有List<T>
)。
如果设计一个人自己的集合类型,可以通过提供ActOnItem
成员来减轻对索引器的限制,从而允许像MyFancyList.ActOnItem(4, (ref Point it) => {it.X += 4;});
这样的代码。提供一系列具有不同数量的附加ref
参数的通用版本可能会有所帮助,这些参数将从调用者传递(例如MyFancyList.ActOnItem(4, (ref MyThing it, ref Thing newValue, ref Thing compareValue) => Threading.Interlocked.CompareExchange(ref it, newValue, compareValue);
),因为使用此类方法可以避免使用lambdas使用捕获的变量。