В чем разница между 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>
на самом деле синтаксически удобный способ вызова метода.
с массивами, однако вы фактически обращаетесь к структуре в памяти. В этом случае нет вызова метода.