// Обратное преобразование ссылки в соответствующее значение short.
short anotherShort = (short)objShort;
Снова обратим ваше внимание на то, что восстановление следует выполнять в соответствующий тип данных. Так, следующая программная логика восстановления из объектного образа генерирует исключение InvalidCastException (обсуждение вопросов обработки исключений содержится в главе 6).
// Некорректное восстановление из объектного образа.
static void Main(string[] args) {
…
try {
// Тип в "yпаковке" - это HE int, a shоrt!
int i = (int)objShort;
} catch(InvalidCastExceptien e) {
Console.WriteLine("ОЙ!n{0} ", e.ToString());
}
}
Примеры создания объектных образов и восстановления значений
Вы, наверное, спросите, когда действительно бывает необходимо вручную выполнять преобразование в объектный тип (или восстановление из объектного образа)? Предыдущий пример был исключительно иллюстративным, поскольку в нем для данных short не было никакой реальной необходимости приведении к объектному типу (с последующим восстановлением данных из объектного образа).
Реальность такова, что необходимость вручную приводить данные к объектному типу возникает очень редко – если возникает вообще. В большинстве случаев компилятор C# выполняет такие преобразования автоматически. Например, при передаче типа, характеризуемого значением, методу, предполагающему получение объектного параметра, автоматически "в фоновом режиме" происходит приведение к объектному типу.
class Program {
static void Main(string[] args) {
// Создание значения int (тип, характеризуемый значением).
int myInt = 99;
// myInt передается методу, предполагающему
// получение объекта, поэтому myInt приводится
// к объектному типу автоматически.
UseThisObject(myInt);
Console.ReadLine();
}
static void UseThisObject(object o) {
Console.WriteLine("Значением о является: {0}", о);}
}
Автоматическое преобразование в объектный тип происходит и при работе c типами библиотек базовых классов .NET. Например, пространство имен System.Collections (формально оно будет обсуждаться в главе 7) определяет тип класса с именем ArrayList. Подобно большинству других типов коллекций, ArrayList имеет члены, позволяющие вставлять, получать и удалять элементы.
public class System.Collections.ArrayList: object, System.Collections.IList, System.Collections.ICollection, System.Collections.IEnumerable, ICloneable {
…
public virtual int Add(object value);
public virtual void Insert(int index, object value);
public virtual void Remove(object obj);
public virtual object this[int index] {get; set;}
}
Как видите, эти члены действуют на типы System.Object. Поскольку все, в конечном счете, получается из этого общего базового класса, следующий программный код оказывается вполне корректным.
static void Main(string [] args) {
…
ArrayList myInts = new ArrayList();
myInts.Add(88);
myInts.Add(3.33);
myInts.Add(false);
}
Но теперь с учетом вашего понимания ссылочных типов и типов, характеризуемые значением, вы можете спросить: что же на самом деле размещается в ArrayList? (Ссылки? Копии ссылок? Копии структур?) Как и в случае, с рассмотренным выше методом UseThisObject(), должно быть ясно, что каждый из типов данных System.Int32 перед размещением в ArrayList в действительности приводится к объектному типу. Чтобы восстановить элемент из типа ArrayList, требуется выполнить соответствующую операцию восстановления.
static void BoxAndUnboxInts() {
// "Упаковка" данных int в ArrayList.
ArrayList myInts = new ArrayList();
myInts.Add(88);
myInts.Add(3.33);
myInts.Add(false);
// Извлечение первого элемента из ArrayList.
int firstItem = (int)myInts[0];
Console.WriteLine("Первым элементом является {0}", firstItem);
}
Без сомнения, операции приведения к объектному типу и восстановления из объектного образа требуют времени и, если эти операций использовать без ограничений, это может влиять на производительность приложения. Но с использованием такого подхода .NET вы можете симметрично работать с типами характеризуемыми значением, и со ссылочными типами.
Замечание. В C# 2.0 потери производительности из-за приведения к ссылочному типу и восстановления из объектного образа можно нивелировать путем использования обобщений (generics), которые будут рассмотрены в главе 10.
Восстановление из объектного образа для пользовательских типов
Когда методу, предполагающему получение экземпляров System.Object, передаются пользовательские структуры или перечни, тоже происходит их приведение к объектному типу, Однако после получения входного параметра вызванным методом вы не сможете получить доступ к каким бы то ни было членам структуры (или перечня), пока не выполните операцию восстановлений из объектного образа для данного типа. Вспомним структуру МуРoint, определенную в этой главе выше.
Struct MyPoint {
public int x, у;
}
Предположим, что вы посылаете переменную MyPoint новому методу с именем UseBoxedMyPoint().
static void Main(string[] args) {
…
MyPoint p;
p.x = 10;
p.y = 20;
UseBoxedMyPoint(p);
}
При попытке получить доступ к полю данных MyPoint возникнет ошибка компиляции, поскольку метод предполагает, что вы действуете на строго типизованный System.Object.
static void UseBoxedMyPoint(object o) {
// Ошибка! System.Object не имеет членов-переменных
// с именами 'х' и 'у' .
Console.WriteLine ("{0}, {1}", о.х, о.у);
}
Чтобы получить доступ к полю данных MyPoint, вы должны сначала восстановить параметр из объектного образа. Сначала можно использовать ключевое слово C# is для проверки того, что этот параметр на самом деле является переменной MyPoint. Ключевое слово is рассматривается в главе 4, здесь мы только предлагаем пример его использования.
static void UseBoxedMyPoint(object о) {
if (о is MyPoint) {
MyPoint p = (MyPoint)o;
Console.WriteLine ("{0}, {1}", p.x, p.y);
} else Console.WriteLine("Вы прислали не MyPoint.");
}
Исходный код. Проект Boxing размещен в подкаталоге, соответствующем главе 3.
Работа с перечнями .NET
Вдобавок к структурам в .NET имеется еще один тип из категории характеризуемых значением – это перечни. При создании программы часто бывает удобно создать набор символьных имен для представления некоторых числовых значений. Например, при создании системы учета оплаты труда работников предприятия вы предпочтете использовать константы Manager (менеджер), Grunt (рабочий), Contractor (подрядчик) и VP (вице-президент) вместо простых числовых значений {0, 1, 2, 3}. Именно по этой причине в C# поддерживаются пользовательские перечни. Например, вот перечень EmpType.
// Пользовательский перечень.
enum EmpType {
Manager, // = 0
Grunt, // = 1
Contractor, // = 2
VP // = 3
}
Перечень EmpType определяет четыре именованные константы, соответствующие конкретным числовым значениям. В C# схема нумерации по умолчанию предполагает начало с нулевого элемента (0) и нумерацию последующих элементов по правилам арифметической прогрессии n + 1. При необходимости вы имеете возможность изменить такое поведение на более удобное.
// начало нумерации со значения 102.
enum EmpType {
Manager = 102,
Grunt, // = 103
Contractor, // =104
VP // = 105
}
Перечни не обязаны следовать строгому последовательному порядку нумерации элементов. Если (по некоторой причине) вы сочтете разумным создать EmpType так, как показано ниже, компилятор примет это без возражений.
// Элементы перечня не обязаны следовать в строгой последовательности!
enum EmpType {
Manager = 10,
Grunt = 1,
Contractor = 100,
VP = 9
}
Тип, используемый для каждого элемента в перечне, по умолчанию отображается в System.Int32. Такое поведение при необходимости тоже можно изменить. Например, если вы хотите, чтобы соответствующее хранимое значение EmpTyре было byte, а не int, вы должны написать следующее.
// Теперь EmpType отображается в byte.
enum EmpType: byte {
Manager = 30,
Grunt = 1,
Contractor = 100,