Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What the difference between array indexer and any other object indexer

Consider following two data types:

class C
{
    public int I { get; set; }
}

struct S
{
    public int I { get; set; }
}

Let's try to use them inside the list, for example:

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

As expected, there is compilation error on line (a): CS1612 Cannot modify the return value of 'List<UserQuery.S>.this[int]' because it is not a variable. This is fine, because actually we trying to modify temporary copy of S, which is r-value in giving context.

But let's try to do same thing for an array:

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

will not compile, as expected.

If we look at the produced IL, we will find following:

IL_0057:  ldloc.1     // s_arr
IL_0058:  ldc.i4.0    // index
IL_0059:  ldelema     UserQuery.S // manager pointer of element

ldelema loads address of the array element to the top of the evaluation stack. Such behavior is expected with fixed array and unsafe pointers. But for safe context this is a bit unexpected. Why there is a special unobvious case for arrays? Any why there is no option to achieve same behavior for members of other types?

like image 374
lorond Avatar asked Aug 03 '15 15:08

lorond


2 Answers

An array access expression is classified as a variable. You can assign to it, pass it by reference etc. An indexer access is classified separately... in the list of classifications (C# 5 spec section 7.1.)

  • An indexer access. Every indexer access has an associated type, namely the element type of the indexer. Furthermore, an indexer access has an associated instance expression and an associated argument list. When an accessor (the get or set block) of an indexer access is invoked, the result of evaluating the instance expression becomes the instance represented by this (§7.6.7), and the result of evaluating the argument list becomes the parameter list of the invocation.

Think of this as similar to the difference between a field and a property:

 public class Test
 {
     public int PublicField;
     public int PublicProperty { get; set; }
 }

 ...

 public void MethodCall(ref int x) { ... }

 ...

 Test test = new Test();
 MethodCall(ref test.PublicField); // Fine
 MethodCall(ref test.PublicProperty); // Not fine

Fundamentally, an indexer is a pair of methods (or a single one) whereas an array access gives you a storage location.

Note that if you weren't using a mutable struct to start with, you wouldn't see the difference in this way - I'd strongly advise against using mutable structs at all.

like image 88
Jon Skeet Avatar answered Nov 02 '22 00:11

Jon Skeet


A class indexer like the one in List<T> is actually a syntactically convenient way of calling a method.

With arrays however you are actually accesing to the structure in memory. There is no method call in that case.

like image 32
Martín Misol Avatar answered Nov 02 '22 01:11

Martín Misol