В чем разница между array indexer и любым другим объектным индексатором

рассмотрим следующие два типа данных:

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

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

давайте попробуем использовать их внутри списка, например:

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

как и ожидалось, ошибка компиляции в строке (a): CS1612 Cannot modify the return value of 'List<UserQuery.S>.this[int]' because it is not a variable. Это хорошо, потому что на самом деле мы пытаемся изменить временную копию S, что является r-значением в контексте.

но давайте попробуем сделать то же самое для массива:

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)

и.. этот завод.

но

var s_arr_list = (IList<S>) s_arr;
s_arr_list[0].I++;

не будет компилироваться, как и ожидалось.

если мы посмотрим на произведенный IL, мы найдем следующее:

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

ldelema загружает адрес элемента массива в верхнюю часть стека оценок. Такое поведение ожидается с fixed массив и небезопасные указатели. Но для безопасного контекста это немного неожиданно. Почему существует специальный неочевидный случай для массивов? Любой, почему нет возможности достичь такого же поведения для члены других типов?

2 ответов


выражение доступа к массиву классифицируется как переменная. Вы можете назначить ему, передать его по ссылке и т. д. Доступ к индексатору классифицируется отдельно... в списке классификаций (C# 5 spec раздел 7.1.)

  • доступ индексатора. Каждый доступ индексатора имеет связанный тип, а именно тип элемента индексатора. Кроме того, доступ индексатора имеет связанное выражение экземпляра и связанный список аргументов. Когда метод доступа (блок get или set) вызывается доступ индексатора, результатом оценки выражения экземпляра становится экземпляр, представленный этим (§7.6.7), а результатом оценки списка аргументов становится список параметров вызова.

подумайте об этом как о разнице между полем и свойством:

 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

принципиально индексатор-это пара методов (или один), тогда как доступ к массиву дает вам хранилище местоположение.

обратите внимание, что если бы вы не использовали изменяемую структуру для начала, вы бы не увидели разницы таким образом - я бы настоятельно рекомендовал вообще не использовать изменяемые структуры.


индексатор классов, как в List<T> на самом деле синтаксически удобный способ вызова метода.

с массивами, однако вы фактически обращаетесь к структуре в памяти. В этом случае нет вызова метода.