Пару недель назад я описал одну ситуацию, возникшую у меня при написании кода. Для тек кто не в курсе вот ссылка на хитрый вопрос. Сегодня я решил более детально рассказать о причинах такого поведения кода.
Для тех, кто не проверял как будет работать описанный код, скажу следующее. В результате его работы мы получим переменную 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 так и остается не инициализированным.
Вот собсвенно и весь секрет. Надеюсь Вам понравилось =).