Шаг 60.
Язык программирования C#. Начала
Массивы. Массив объектных ссылок

    На этом шаге мы рассмотрим создание и использование таких массивов.

    Ранее мы всегда явно или неявно имели в виду, что элементы массива относятся к одному типу. В общем-то, это так. Но бывают и неординарные ситуации. Как мы узнаем немного позже, в языке C# в пространстве имен System есть один особый класс, который называется Object. Его "особенность" связана с тем, что переменная типа Object может ссылаться на значение любого типа. Проще говоря, если объявить переменную и идентификатором типа для нее указать имя класса Object, то значением такой переменной можно присвоить практически все, что угодно.


В языке C# существует механизм наследования, который позволяет создавать один класс (производный класс) на основе другого класса (базовый класс). В результате получается иерархия классов, связанных наследованием, когда класс создается на основе класса, и на его основе создается еще один класс, и так далее. В наследовании есть важный момент: переменная базового класса может ссылаться на объекты, созданные на основе производных классов. Специфика класса Object состоит в том, что он находится в вершине иерархии наследования и любой класс является его прямым или косвенным потомком. Поэтому переменная типа Object может ссылаться на любой объект. Более подробно методы работы с классами и объектами, в том числе и механизм наследования, буду рассмотрены в последующих шагах.

    Если создать массив из элементов типа Object, то элементам такого массива можно присваивать значения разных типов. В некоторых случаях это бывает удобно и полезно.


На практике вместо названия класса Object обычно используют идентификатор object, который является псевдонимом для выражения System.Object.

    Небольшой пример, в котором создается и используется массив с элементами типа Object, приведен ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr60_1
{
    class Program
    {
        static void Main()
        {
            // Массив из трех переменных типа Object:
            Object[] objs = new Object[3];
            // Элементам массива присваиваются значения 
            // разных типов:
            objs[0] = 123; // Целое число
            objs[1] = 'А'; // Символ
            objs[2] = "Третий элемент"; // Текст 
            Console.WriteLine("Создан такой массив:");
            // Проверка содержимого массива: 
            for(int k = 0; k < objs.Length; k++){
                Console.WriteLine(k + ": " + objs[k]);
            }
            // Новые значения элементов:
            objs[0] = (int)objs[0] + 111; // Целое число
            objs[1] = "Bтopой элемент"; // Текст
            objs[2] = 3.141592; // Действительное число
            Console.WriteLine("После присваивания значений:");
            // Проверка содержимого массива:
            for(int k = 0; k < objs.Length; k++){
                Console.WriteLine(k + ": " + objs[k]);
            }
            // Целочисленный массив: 
            int[] nums = {10, 20, 30};
            // Переменная массива присваивается как значение 
            // элементу массива: 
            objs[2] = nums;
            Console.WriteLine("Целочисленный массив:");
            // Отображение элементов целочисленного массива: 
            for(int i = 0; i < ((int[])objs[2]).Length; i++){
                Console.Write("{0,3}", ((int[])objs[2])[i]);
            }
            Console.WriteLine();
            // Новое значение элемента в числовом массиве:
            ((int[])objs[2])[1] = 0;
            Console.WriteLine("Еще раз тот же массив:");
            // Отображение элементов целочисленного массива: 
            for(int i = 0; i < nums.Length; i++){
                Console.Write("{0,3}", nums[i]);
            }
            Console.WriteLine();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


Рис.1. Результат работы приложения

    Массив из трех переменных типа Object создается с помощью команды

  Object[] objs = new Object[3];             . 
После создания массива его элементам присваиваются значения. Делается это в явном виде, но пикантность ситуации в том, что присваиваемые значения относятся к разным типам. Так, командой
  objs[0] = 123;
первому элементу массива objs значением присваивается целочисленный литерал 123. Командой
  objs[1] = 'А';
второму элементу массива objs присваивается символьное значение 'А', а командой
  objs[2] = "Третий элемент";
третьему элементу массива objs присваивается текстовое значение "Третий элемент". С помощью оператора цикла убеждаемся, что значения элементов именно такие.

    На следующем этапе элементам массива присваиваются новые значения. Причем тип присваиваемых значений может не совпадать с типом текущего значения элемента. Здесь все просто. Некоторых пояснений требует лишь команда

  objs[0] = (int)objs[0] + 111;              .
Все дело в том, что на момент выполнения команды фактическим значением элемента objs[0] является целое число. Что мы пытаемся сделать - так это к текущему значению элемента прибавить число 111. И вот здесь сталкиваемся с ситуацией, которая является платой за возможность присваивать переменным типа Object значения разных типов: если мы хотим, чтобы с элементом objs[0] выполнялась арифметическая операция и сам элемент обрабатывался как целочисленное значение, то необходимо этот факт в явном виде отобразить. Именно поэтому в правой части выражения перед ссылкой на элемент objs[0] использована инструкция явного приведения типа (int). Фактически, используя выражение вида (int)objs[0], мы даем команду обрабатывать значение элемента objs[0] как целое число типа int.

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

    На следующем этапе командой

  int[] nums = {10, 20, 30};
создается целочисленный массив из трех элементов со значениями 10, 20 и 30. Это обычный массив. Не очень обычен способ его использования: командой
  objs[2] = nums;
переменную массива nums мы присваиваем значением элементу objs[2]. Значение переменной nums - это ссылка на массив из трех чисел. После выполнения команды
  objs[2] = nums;
значением элемента objs[2] является ссылка на точно тот же массив. Поэтому мы вполне можем использовать для доступа к массиву элемент objs[2]. Но если так, то мы должны в явном виде указать, как следует обрабатывать значение элемента objs[2] - используется инструкция приведения типа (int[]), означающая, что значение элемента является ссылкой на целочисленный массив. Так мы поступаем в цикле, где индексная переменная i принимает значения от 0 и до некоторого значения: переменная i должна быть строго меньше значения ((int[])objs[2]).Length, которое является размером массива, на который ссылается элемент objs[2]. Выражение ((int[])objs[2]).Length - это значение свойства Length для элемента objs[2], если значение элемента интерпретировать как ссылку на одномерный целочисленный массив (значение типа int[]). В теле конструкции цикла ссылка на элементы массива, на который ссылается элемент objs[2], выполняется в формате ((int[])objs[2])[i]: сначала значение элемента objs[2] приводится к типу int[], а затем то, что получилось, индексируется (в квадратных скобках указывается индекс элемента).


Число 3 в инструкции {0,3} в команде
  Console.Write("{0,3}", ((int[])objs[2])[i]);
означает, что при отображении значения элемента числового массива выделяется не менее трех позиций.

    Убедиться в том, что переменная nums и элемент objs[2] ссылаются на один и тот же массив, достаточно легко. Командой

  ((int[])objs[2])[1] = 0;
меняем значение второго (по порядку) элемента в массиве, на который ссылается элемент objs[2], а затем с помощью цикла проверяем содержимое массива nums. Несложно заметить, что значение элемента nums[1] действительно изменилось.

    На следующем шаге мы рассмотрим параметры командной строки.




Предыдущий шаг Содержание Следующий шаг