What is the difference between List <T> and array indexers?
The question raised in my mind after Is it possible to access the link structure from the list to make changes? thread by reza.
So, consider the following struct and interface (definitely not very useful, but just to show the problem):
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 its box in a bunch without unpacking and replace it with a new one. This can be illustrated by the following code:
MyStruct[] l1 = new MyStruct[] { new MyStruct(0) }; Console.WriteLine(l1[0].Value); //0 l1[0].Change(10); Console.WriteLine(l1[0].Value); //10 Now change the array to List<T> , i.e.:
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 I understand it, in the first case l1[0] returned the reference to the box structure, and in the second it was smth else.
I also tried to parse this and found:
1) For 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> :
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 didn’t seem ready to interpret it well.
So what is List<T> ? Or how does an array and List<T> return elements by index? Or does this apply only to value types and has nothing to do with reference types?
PS: I do understand that one should not change an instance of a value type, but the described problem made me understand, I never understood how List<T> and array work.
.Net can access the array elements in place using the ldelema command (load address of the array element).
This allows you to work directly with array elements without copying them. (so you can pass an array element as a parameter to ref or out )
List<T> does not have this 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 structure.
The array indexer makes the element accessible to the following code in a way similar to passing it as a ref parameter. In any .net language, there is no mechanism for any other type. Any other type that allows indexing access should expose a couple of methods, one of which makes a copy of the internal data available to the caller’s code, and one of them will, if it has a copy of some data from the caller’s code, that the data is in some way. This restriction is most noticeable with type values, but in some cases it can also be problematic for reference types (for example, it is possible to execute Interlocked.ComapreExchange for an element in T[] , but not for an element with List<T> ).
If you are developing your own collection types, you can reduce the limit on indexers by proposing an ActOnItem member, thereby allowing you to use code like MyFancyList.ActOnItem(4, (ref Point it) => {it.X += 4;}); . It may be useful to provide a family of generic versions with a different number of additional ref parameters that will be passed 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 using such methods can avoid the need to use lambdas for the captured variables.