C#: Ответ за «засыпчатный» вопрос

Пару недель назад я описал одну ситуацию, возникшую у меня при написании кода. Для тек кто не в курсе вот ссылка на хитрый вопрос. Сегодня я решил более детально рассказать о причинах такого поведения кода.

Для тех, кто не проверял как будет работать описанный код, скажу следующее. В результате его работы мы получим переменную a с инициализированным атрибутом Columns, содержащим один элемент. При этом атрибут Numbers у первого элемента Columns будет равен null. Собственно в этом null значении и кроется все самое интересное, так как судя по коду, в атрибуте Numbers должно находится три значения (1, 2, 3).
В предыдущей записи, оговаривалось, что чтобы разобраться в данной ситуации нужно знать как устроен язык программирования (в данном случае C#). Если говорить более конкретно, то речь идет от различие структур и классов. Если Вы читали мои заметки, то возможно видели запись о различие классов и структур в С#. Если же Вы не читали данную замету и не знаете в чем разница, то советую ознакомится с ней, перед тем как читать дальше.

Теперь мы точно знаем разницу между классом и структурой 🙂 Теперь вернемся в нашему коду, не забывая, что A и B это структуры:

A a = new A(); //1: Создаем объект стукрутры A
a.Columns = new List(); //2: У созданого объекта инициализируем атрибут Columns 
a.Columns.Add(new A.B()); //3: Добавляем в Columns новый объект структуры B
a.Columns[0].Add(1); //4: ? Добавляем в первый элемент Columns (объект B из строчки 3) значение 1
a.Columns[0].Add(2); //5: ? Добавляем в первый элемент Columns (объект B из строчки 3) значение 2
a.Columns[0].Add(3); //6: ? Добавляем в первый элемент Columns (объект B из строчки 3) значение 3
Console.ReadKey(); //7: Останавливаем код (ждем надатия любой клавиши на клавиатуре), чтобы консоль не закрылась сразу

И остановимся на строчках 4, 5 и 6. На самом деле они полностью идентичны, поэтому рассмотрим только строчку 4. Как видно из результата выполнения программы, данные строки «не срабатывают». Вспомним, что B это структруа. А список Columns — список типа B. Это означает, что любые операции возврата и присваивания работают по значению. Обратим вниание на операцию a.Columns[0]. Тут стоит вспомнить, что это на самом деле вызов функции следующего вида:

public TYPE this[int idx] 
{
   get
   {
      if(idx < this._arrayOfTYPE.Length)
         return this._arrayOfTYPE[idx];
      throw new IndexOutOfRangeException();
   }
}

А значит мы имеем дело с возвращаемым по значению значением. Другими словами a.Columns[0] возвращает не первый элемент списка Columns, а его точную копию. А операция Add уже вызывается на копии нашего объекта структуры B. После выполнения функции Add наша копия теряется. А объект из строчки 3 так и остается не инициализированным.

Вот собсвенно и весь секрет. Надеюсь Вам понравилось =).

Запись опубликована в рубрике C# с метками , , , , , , , , , . Добавьте в закладки постоянную ссылку.