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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With