List 和数组索引器之间有什么区别?

时间:2021-02-05 13:25:06

The question raised in my mind after Is it possible to access a reference of a struct from a List to make changes? thread by reza.

在我的脑海中提出的问题是否可以从List中访问结构的引用以进行更改?由reza线程。

So, consider the following struct and interface (definetely not very useful, but just to show the issue):

因此,请考虑以下结构和接口(definetely不是很有用,但只是为了显示问题):

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 implements IChangeStruct, so we can change a boxed copy of it right in the heap without unboxing and replacing with a new one. This can be demostrated with the following code:

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

Now, let's change array to List<T>, i.e.:

现在,让我们将数组更改为List ,即:

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

As far as I understood, in the first case l1[0] returned the referense to the boxed struct, while in the second - it was smth else.

据我所知,在第一种情况下,l1 [0]将引用返回到盒装结构,而在第二种情况下 - 它是其他的。

I also tried to disassemble this and found:

我也试图反汇编这个并发现:

1) For MyStruct[]:

1)对于MyStruct []:

IL_0030:  ldelema    Utils.MyStruct
IL_0035:  ldc.i4.s   10
IL_0037:  call       instance void Utils.MyStruct::Change(int32)

2) For List<MyStruct>:

2)对于List :

 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)

But I appeared to be not ready to interpret it well.

但我似乎还没有准备好解释它。

So, what did the List<T> return? Or how do array and List<T> return elements by index? Or is this only the case with value types and has nothing to do with reference types?

那么,List 返回了什么?或者array和List 如何通过索引返回元素?或者这只是值类型的情况,与引用类型无关?

P.S.: I do understand that one must not change a value type instance, but the described issue made me understand, I never realized how List<T> and array work.

P.S。:我确实理解一个人不能改变一个值类型实例,但所描述的问题让我理解,我从未意识到List 和数组是如何工作的。

2 个解决方案

#1


9  

.Net can address array elements in-place, using the ldelema instruction (load address of array element).

.Net可以使用ldelema指令(数组元素的加载地址)就地寻址数组元素。

This allows you to operate directly on array elements without copying them. (this is also why you can pass an array element as a ref or out parameter)

这允许您直接在数组元素上操作而无需复制它们。 (这也是你可以将数组元素作为ref或out参数传递的原因)

List<T> has no such capability. Instead, list[i] is just syntactic sugar for list.get_Item(i), which is a normal method call that returns a copy of the struct.

List 没有这样的能力。相反,list [i]只是list.get_Item(i)的语法糖,这是一个返回结构副本的普通方法调用。

#2


3  

The indexer of an array makes an element available to the following code in a manner similar to passing it as a ref parameter. No mechanism exists in any .net language for any other type to behave likewise. Any other type which allows indexed access must expose a pair of methods, one of which makes a copy of the internally-stored data available to the caller's code, and one of which will, given a copy of some data from the caller's code, store that data in some fashion. This limitation is most visible with value types, but may in some cases also be problematic with reference types (e.g. it's possible to perform an Interlocked.ComapreExchange on an element in a T[], but not on an element with a List<T>).

数组的索引器以类似于将其作为ref参数传递的方式使元素可用于以下代码。对于任何其他类型,任何.net语言都不存在任何机制。允许索引访问的任何其他类型必须公开一对方法,其中一个方法将内部存储数据的副本提供给调用者的代码,其中一个方法将给出来自调用者代码的一些数据的副本,存储那些数据以某种方式。这种限制在值类型中最明显,但在某些情况下也可能对引用类型有问题(例如,可以对T []中的元素执行Interlocked.ComapreExchange,但不能对具有List 的元素执行)。

If one is designing one's own collection types, one may ease the limitation on indexers by offering an ActOnItem member, thus allowing code like MyFancyList.ActOnItem(4, (ref Point it) => {it.X += 4;});. It may be helpful to provide a family of generic versions with different numbers of additional ref parameters which would be passed through from the caller (e.g. MyFancyList.ActOnItem(4, (ref MyThing it, ref Thing newValue, ref Thing compareValue) => Threading.Interlocked.CompareExchange(ref it, newValue, compareValue);) since use of such methods can avoid the need for lambdas to use captured variables.

如果设计一个自己的集合类型,可以通过提供一个ActOnItem成员来减轻对索引器的限制,从而允许像MyFancyList.ActOnItem(4,(ref Point it)=> {it.X + = 4;})这样的代码; 。提供一系列具有不同数量的附加ref参数的通用版本可能会有所帮助,这些参数将从调用者传递(例如,MyFancyList.ActOnItem(4,(参考MyThing it,ref Thing newValue,ref Thing compareValue)=>线程.Interlocked.CompareExchange(ref it,newValue,compareValue);)因为使用这样的方法可以避免使用lambdas来使用捕获的变量。

#1


9  

.Net can address array elements in-place, using the ldelema instruction (load address of array element).

.Net可以使用ldelema指令(数组元素的加载地址)就地寻址数组元素。

This allows you to operate directly on array elements without copying them. (this is also why you can pass an array element as a ref or out parameter)

这允许您直接在数组元素上操作而无需复制它们。 (这也是你可以将数组元素作为ref或out参数传递的原因)

List<T> has no such capability. Instead, list[i] is just syntactic sugar for list.get_Item(i), which is a normal method call that returns a copy of the struct.

List 没有这样的能力。相反,list [i]只是list.get_Item(i)的语法糖,这是一个返回结构副本的普通方法调用。

#2


3  

The indexer of an array makes an element available to the following code in a manner similar to passing it as a ref parameter. No mechanism exists in any .net language for any other type to behave likewise. Any other type which allows indexed access must expose a pair of methods, one of which makes a copy of the internally-stored data available to the caller's code, and one of which will, given a copy of some data from the caller's code, store that data in some fashion. This limitation is most visible with value types, but may in some cases also be problematic with reference types (e.g. it's possible to perform an Interlocked.ComapreExchange on an element in a T[], but not on an element with a List<T>).

数组的索引器以类似于将其作为ref参数传递的方式使元素可用于以下代码。对于任何其他类型,任何.net语言都不存在任何机制。允许索引访问的任何其他类型必须公开一对方法,其中一个方法将内部存储数据的副本提供给调用者的代码,其中一个方法将给出来自调用者代码的一些数据的副本,存储那些数据以某种方式。这种限制在值类型中最明显,但在某些情况下也可能对引用类型有问题(例如,可以对T []中的元素执行Interlocked.ComapreExchange,但不能对具有List 的元素执行)。

If one is designing one's own collection types, one may ease the limitation on indexers by offering an ActOnItem member, thus allowing code like MyFancyList.ActOnItem(4, (ref Point it) => {it.X += 4;});. It may be helpful to provide a family of generic versions with different numbers of additional ref parameters which would be passed through from the caller (e.g. MyFancyList.ActOnItem(4, (ref MyThing it, ref Thing newValue, ref Thing compareValue) => Threading.Interlocked.CompareExchange(ref it, newValue, compareValue);) since use of such methods can avoid the need for lambdas to use captured variables.

如果设计一个自己的集合类型,可以通过提供一个ActOnItem成员来减轻对索引器的限制,从而允许像MyFancyList.ActOnItem(4,(ref Point it)=> {it.X + = 4;})这样的代码; 。提供一系列具有不同数量的附加ref参数的通用版本可能会有所帮助,这些参数将从调用者传递(例如,MyFancyList.ActOnItem(4,(参考MyThing it,ref Thing newValue,ref Thing compareValue)=>线程.Interlocked.CompareExchange(ref it,newValue,compareValue);)因为使用这样的方法可以避免使用lambdas来使用捕获的变量。