Индексаторы в Списке против Массива - PullRequest
9 голосов
/ 15 июля 2011

Как индексаторы определены в списках и массивах.

List<MyStruct> lists=new List<MyStruct>();, где MyStruct - это структура. Теперь рассмотрим MyStruct[] arr=new MyStruct[10];

arr[0] дает ссылку на первый элемент структуры. Но lists[0] дает мне его копию. Есть ли какая-то причина, почему это делается так? Также, поскольку Int32 является структурой List<Int32> list1 =new List<Int32>();, как я могу получить доступ к list1[0] или назначить list1[0]=5 там, где это невозможно сделать lists[0]._x=5

Ответы [ 6 ]

9 голосов
/ 15 июля 2011

Хотя они выглядят одинаково, индексатор массива и индексатор списка делают совершенно разные вещи.

Индексатор List<T> объявлен как свойство с параметром:

public T this[int index] { get; set; }

Он компилируется в get_Item и set_Item методы, которые вызываются как любой другой метод при обращении к параметру.

Индексатор массива имеет прямую поддержку в CLR;Существует специальная инструкция IL ldelema (адрес элемента загрузки) для получения управляемого указателя на n-й элемент массива.Этот указатель может затем использоваться любой из других инструкций IL, которые принимают указатель для непосредственного изменения объекта по этому адресу.

Например, инструкция stfld (значение поля хранилища) может принимать управляемый указательуказав экземпляр 'this' для хранения поля, или вы можете использовать указатель для вызова методов непосредственно для объекта в массиве.

На языке C # индексатор массива возвращает переменную , но индексатор списка возвращает значение .

5 голосов
/ 15 июля 2011

Конечная точка:

lists[0]._x=5

на самом деле просто подтверждение вашей предыдущей точки:

arr[0] дает ссылку на первый элемент структуры. Но lists[0] дает мне его копию.

Если вы отредактируете копию этого изменения, оно будет потеряно в эфире, т.е.

var throwawayCopy = lists[0];
throwawayCopy._x = 5;
// (and never refer to throwawayCopy again, ever)

Поскольку это почти наверняка не то, что вы хотели, компилятор не позволяет вам. Однако изменчивые структуры являются злом . Лучшим вариантом здесь будет не использовать изменяемые структуры . Они кусаются.


Понижение уровня до простого, но конкретного примера:

using System;
struct Evil
{
    public int Yeuch;
}
public class Program
{
    public static void Main()
    {
        var pain = new Evil[] { new Evil { Yeuch = 2 } };
        pain[0].Yeuch = 27;
        Console.WriteLine(pain[0].Yeuch);
    }
}

Это компилирует (глядя на последние 2 строки здесь) как:

L_0026: ldloc.0 <== pain
L_0027: ldc.i4.0 <== 0
L_0028: ldelema Evil <== get the MANAGED POINTER to the 0th element
                           (not the 0th element as a value)
L_002d: ldc.i4.s 0x1b <== 27
L_002f: stfld int32 Evil::Yeuch <== store field

L_0034: ldloc.0 <== pain
L_0035: ldc.i4.0 <== 0
L_0036: ldelema Evil <== get the MANAGED POINTER to the 0th element
                           (not the 0th element as a value)
L_003b: ldfld int32 Evil::Yeuch <== read field
L_0040: call void [mscorlib]System.Console::WriteLine(int32) <== writeline
L_0045: ret 

Он никогда не говорит со структурой как значением - без копий и т. Д.

2 голосов
/ 28 января 2012

Я столкнулся с этим также, когда я проверял типы лямбда-выражений. Когда лямбда-код компилируется в дерево выражений, вы можете проверить тип выражения для каждого узла. Оказывается, существует специальный тип узла ArrayIndex для индексатора Array:

Expression<Func<string>> expression = () => new string[] { "Test" }[0];
Assert.Equal(ExpressionType.ArrayIndex, expression.Body.NodeType);

Принимая во внимание, что индексатор List имеет тип Call:

Expression<Func<string>> expression = () => new List<string>() { "Test" }[0];
Assert.Equal(ExpressionType.Call, expression.Body.NodeType);

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

2 голосов
/ 15 июля 2011

List<T> имеет нормальный индексатор, который ведет себя как свойство.Доступ осуществляется через функции доступа, которые являются побочными.

T this[int index]
{
    get{return arr[index];}
    set{arr[index]=value;}}
}

Массивы являются специальными типами, а их индексатор подобен полям.И среда выполнения, и компилятор C # обладают специальными знаниями о массивах, и это обеспечивает такое поведение.Вы не можете иметь поведение, подобное массиву, для пользовательских типов.

К счастью, на практике это редко является проблемой.Поскольку вы используете изменяемые структуры только в редких особых случаях (с высокой производительностью или встроенным взаимодействием), а в тех случаях, когда вы предпочитаете массивы, вы все равно предпочитаете их из-за низких издержек.поля.При использовании поля вы получаете своего рода ссылку, а при использовании свойства - копию.Таким образом, вы можете писать в члены полей типа значения, но не в члены свойств типа значения.

0 голосов
/ 29 января 2012

Одно досадное ограничение языков .net состоит в том, что они не имеют никакого понятия о свойстве, которое делает что-либо, кроме возврата значения, которое затем может быть использовано, если вызывающая сторона сочтет нужным. Было бы очень полезно (и если бы у меня были средства подачи петиций для языковых функций, я бы искал это), если бы существовали стандартные поддерживаемые компилятором средства представления свойств как вызывающих делегатов, например, такой оператор:

  MyListOfPoints[4].X = 5;

может быть переведено компилятором во что-то вроде:

  MyListOfPoints.ActOnItem(4, (ref Point it) => it.X = 5);

Такой код может быть относительно эффективным и не создавать никакого давления GC, если ActOnItem принимает дополнительный параметр ref универсального типа и передает его делегату, который также принимает параметр этого типа. Это позволит статически вызывать вызываемую функцию, исключая необходимость создания замыканий или делегатов для каждого выполнения включающей функции. Если бы у ActOnItem был способ принять переменное число общих параметров 'ref', можно было бы даже обрабатывать такие конструкции, как:

  SwapItems(ref MyListOfPoints[4].X, ref MyListofPoints[4].Y);

с произвольными комбинациями параметров 'ref', но даже просто с возможностью обрабатывать случаи, когда свойство было "задействовано" слева от присваивания, или функция была вызвана с единственным свойством-ref 'ref' параметр, было бы полезно.

Обратите внимание, что возможность делать вещи таким образом принесет дополнительную выгоду помимо возможности доступа к полям структур. Это также будет означать, что объект, выставляющий свойство, получит уведомление о том, что с ним покончил потребитель (так как делегат потребителя вернется). Представьте, например, что у вас есть элемент управления, который показывает список элементов, каждый со строкой и цветом, и кто-то хочет иметь возможность сделать что-то вроде:

  MyControl.Items(5).Color = Color.Red;

Простой оператор для чтения и самый естественный способ изменить цвет пятого элемента списка, но попытка заставить такой оператор работать, потребует, чтобы объект, возвращаемый Items(5), имел ссылку на MyControl и отправьте уведомление, когда оно изменилось. Скорее сложно. В отличие от этого, если бы поддерживался указанный выше стиль сквозного вызова, такая вещь была бы намного проще. Метод ActOnItem(index, proc, param) будет знать, что после возвращения proc ему придется перерисовать элемент, указанный в index. Немаловажно, если бы Items(5) был процедурой сквозного вызова и не поддерживал какой-либо метод прямого чтения, можно было бы избежать таких сценариев, как:

  var evil = MyControl.Items(5);
  MyControl.items.DeleteAt(0);
  // Should the following statement affect the item that used to be fifth,
  // or the one that's fifth now, or should it throw an exception?  How
  // should such semantics be ensured?
  evil.Color = Color.Purple;

Значение MyControl.Items(5) останется связанным с MyControl только на время сквозного вызова с его участием. После этого это будет просто отдельное значение.

0 голосов
/ 15 июля 2011

Ваша проблема не в List <>, а в самих структурах.

Возьмем для примера:

public class MyStruct
{
    public int x;
    public int y;
}

void Main()
{
    var someStruct = new MyStruct { x = 5, y = 5 };

    someStruct.x = 3;
}

Здесь вы не изменяете значение xисходная структура, вы создаете новый объект с y = y и x = 3. Причина, по которой вы не можете напрямую изменить это с помощью списка, заключается в том, что индексатор списка является функцией (в отличие от индексатора массива)и он не знает, как «установить» новую структуру в списке.

Измените ключевое слово struct на class, и вы увидите, что оно работает просто отлично (с классами выне создавать новый объект каждый раз, когда вы меняете его).

...