- •Тема 6. Основы программирования на языке c#
- •Условная операция
- •Операции checked и unchecked
- •Операция поглощения null
- •Безопасность типов
- •Преобразования типов
- •Неявные преобразования
- •Явные преобразования
- •Проверка равенства объектов
- •Упаковка и распаковка
- •Виртуальный метод Equals()
- •Статический метод Equals()
- •Проверка типов значений на равенство
- •Перегрузка операций
- •Как работают операции
- •Пример перегрузки операции: структура Vector
- •Добавление дополнительных перегрузок
- •Перегрузка операций сравнения
- •Пользовательские приведения
- •Реализация пользовательских приведений
- •Приведение между классами
- •Приведение между базовым и производным классами
- •Упаковывающие и распаковывающие приведения
- •Множественные приведения
Упаковка и распаковка
В разделе 6.2 вы узнали, что все типы простые предопределенные вроде int и char, а также сложные наподобие классов и структур наследуются от типа object. Это значит, что даже с литералами можно обращаться как с объектами:
string s = 10.ToString();
Однако вы также видели, что типы данных C# подразделяются на типы значений, которые размещаются в стеке, и ссылочные типы, размещаемые в куче. Как объяснить возможность вызова методов для int, если int не более чем четырехбайтовое значение в стеке?
Способ, который использует C# для достижения такого эффекта, называется упаковкой (boxing); Упаковка и ее противоположность распаковка (unboxing) позволяют преобразовывать типы значений в ссылочные типы и затем обратно в типы значений. Эта тема была включена в раздел, посвященный приведению, потому, что, по сути, при этом выполняется приведение значения к типу object. Упаковка термин, применяемый для описания трансформации типа значения в ссылочный тип. В основном, при этом исполняющая среда создает временный “ящик” типа ссылки на объект в куче.
Такое преобразование может происходить неявно, как в предыдущем примере, но его можно также выполнять явно:
int myIntNumber = 20;
object myObject = myIntNumber;
Распаковка это термин, описывающий обратный процесс, когда ранее упакованная величина приводится обратно к типу значений. Здесь используется термин приведение, поскольку это должно быть сделано явно. Используемый синтаксис подобен тому, что применяется при явном приведении типов, описанном выше:
int myIntNumber = 20;
object myObject = mylntNumber; // Упаковать int
int mySecondNumber = (int)myObject; // Распаковать обратно в int
Вы можете распаковать только ту переменную, которая была ранее упакована. Если выполнить последнюю строку, когда myObject не является упаковкой для int, во время выполнения будет сгенерировано исключение.
Одно важное предостережение: при распаковке следует соблюдать осторожность, чтобы принимающая переменная имела в себе достаточно места для сохранения всех байтов распаковываемого значения. Например, int в C# имеет длину только 32 бита, а потому распаковка значения long (длиной 64 бита) в int, как показано ниже, приведет к генерации исключения InvalidCastException:
long myLongNumber = 333333423;
object myObject = (object)myLongNumber;
int mylntNumber = (int)myObject;
Проверка равенства объектов
После того, как мы обсудили операции в целом и кратко коснулись операции равенства в частности, стоит задуматься что может означать равенство, когда речь идет о классах и структурах? Понимание механики сравнения объектов существенно для программирования логических выражений, а также важно для реализации перегрузок и приведений. Это тема, на которой будет сосредоточено внимание в оставшейся части главы.
Механизмы сравнения объектов различаются в зависимости от того, сравниваются ли ссылочные типы (экземпляры классов) или же типы значений (примитивные типы данных, экземпляры структур или перечислений). В последующих разделах мы независимо рассмотрим сравнение ссылочных типов и типов значений.
Сравнение ссылочных типов на эквивалентность
Один аспект System.Object, который на первый взгляд может показаться неожиданным это тот факт, что в нем определены три разных метода ддя проверки эквивалентности объектов: ReferenceEquals() и две версии Equals(). Добавьте сюда операцию сравнения (==) и вы получите четыре способа проверки равенства. Между этими способами существуют тонкие отличия, которые описаны ниже.
Метод ReferenceEquals()
ReferenceEquals() статический метод, проверяющий, указывают ли две ссылки на один и тот же экземпляр класса; точнее содержат ли две ссылки один и тот адрес памяти. Поскольку это статический метод, который невозможно переопределить, его реализация из System.Object это то, что доступно всегда. ReferenceEquals() всегда будет возвращать true, когда ему передаются две ссылки, указывающие на один и тот же экземпляр объекта, и false в противном случае. Однако две ссылки на null трактуются как равные:
SomeClass х, у;
х = new SomeClass() ;
у = new SomeClass();
bool В1 = ReferenceEquals(null,null); //возвращает true
bool B2 = ReferenceEquals(null,x); //возвращает false
bool B3 = ReferenceEquals (x, у) ; //возвращает false, потому что
//x и у указывают на разные объекты
