6

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

因此,请考虑以下structinterface(绝对不是很有用,而只是为了说明问题):

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;
    }
}

MyStructimplements 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>数组是如何工作的。

4

2 回答 2

9

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

这使您可以直接对数组元素进行操作而无需复制它们。(这也是您可以将数组元素作为参数传递ref的原因out

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

于 2012-11-21T04:09:03.100 回答
3

数组的索引器以类似于将元素作为ref参数传递的方式使元素可用于以下代码。任何 .net 语言中都不存在任何其他类型的行为相同的机制。任何其他允许索引访问的类型都必须公开一对方法,其中一个方法将内部存储的数据的副本提供给调用者的代码,其中一个将在给定调用者代码中的一些数据的副本的情况下存储这些数据以某种方式。这种限制在值类型中最为明显,但在某些情况下,引用类型也可能存在问题(例如,可以对Interlocked.ComapreExchangea 中的元素执行 an T[],但不能对具有 a 的元素执行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);)传递过来,因为使用此类方法可以避免 lambda 使用捕获的变量。

于 2012-11-21T18:11:37.637 回答