else return false;
}
return false;
}
Теперь предположим, что у нас есть тип Car (автомобиль), экземпляр которого мы попытаемся передать методу Person.Equals().
// Автомобили – это не люди!
Car с = new Car();
Person p = new Person();
p.Equals(c);
Из-за проверки в среде выполнения на "истинность" объекта Person (с помощью оператора is) метод Equals() возвратит false. Теперь рассмотрим следующий вызов.
// Ой!
Person р = new Person();
p.Equals(null);
Это тоже не представляет опасности, поскольку наша проверка предусматривает возможность поступления пустой ссылки.
Переопределение System.Object.GetHashCode()
Если класс переопределяет метод Equals(), следует переопределить и метод System.Object.GetHashCode(). Не сделав этого, вы получите предупреждение компилятора. Роль GetHashCode() – возвратить числовое значение, которое идентифицирует объект в зависимости от его состояния. И если у вас есть два объекта Person, имеющие идентичные значения name, SSN и age, то вы должны получить для них одинаковый хеш-код.
Вообще говоря, переопределение этого метода может понадобиться только тогда, когда вы собираетесь сохранить пользовательский тип в коллекции, использующей хеш-коды, например, в System.Collections.Hashtable. В фоновом режиме тип Hashtable вызывает Equals() и GetHashCode() содержащихся в нем типов, чтобы определить правильность объекта, возвращаемого вызывающей стороне. Поскольку System.Object не имеет информации о данных состояния для производных типов, вы должны переопределить GetHashCode() для всех типов, которые вы собираетесь хранить в Hashtable.
Есть много алгоритмов, которые можно использовать для создания хеш-кода, как "изощренных", так и достаточно "простых". Еще раз подчеркнем, что значение хеш-кода объекта зависит от состояния этого объекта. Класс System.String имеет довольно солидную реализацию GetHashCode(), основанную на значении символьных данных. Поэтому, если можно найти строковое поле, которое будет уникальным для всех рассматриваемых объектов (например, поле SSN для объектов Person), то можно вызвать GetHashCode() для строкового представлении такого поля.
// Возвращает хеш-код на основе SSN.
public override int GetHashCode() {
return SSN.GetHashCode();
}
Если вы не сможете указать подходящий элемент данных, но переопределите ToString(), то можно просто возвратить хеш-код строки, возвращенной вашей реализацией ToString().
// Возвращает хеш-код на основе пользовательского ToString().
public override int GetHashCode() {
return ToString().GetHashCode();
}
Тестирование переопределенных членов
Теперь можно проверить обновленный класс Person. Добавьте следующий программный код в метод Main() и сравните результат его выполнения с тем, что показано на рис. 3.18.
static void Main (string[] args) {
// ВНИМАНИЕ: эти объекты должны быть идентичными.
Person р3 = new Person("Fred", "Jones", "222-22-2222", 98);
Person p4 = new Person("Fred", "Jones", "222-22-2222", 98);
// Тогда эти хеш-коды и строки будут одинаковыми.
Console.WriteLine("-› Хеш-код для р3 = {0}", р3.getHashCode());
Console.WriteLine("-› Хеш-код для р4 = {0}", p4.GetHashCode());
Console.WriteLine("-› Строка для р3 = {0}", p3.ToString());
Console.WriteLine("-› Cтрока для р4 = {0}", p4.ToString());
// Здесь состояния должны быть одинаковыми.
if (р3.Equals(p4)) Console.WriteLine("-› Состояния р3 и р4 одинаковы!");
else Console.WriteLine("-› Состояния р3 и р4 различны!");
// Изменим age для р4.
Console.WriteLine("n-› Изменение age для р4n");
р4.age = 2;
// Теперь состояния неодинаковы: хеш-коды и строки будут разными.
Console.WriteLine("-› Строка для р3 = {0}", p3.ToString());
Console.WriteLine("-› Строка для р4 = {0}", p4.ToString());
Console.WriteLine("-› Хеш-код для р3 = {0}", р3.GetHashCode());
Console.WriteLine("-› Хеш-код для р4 = {0}", p4.GetHashCode());
if (р3.Equals(p4)) Console.WriteLine("-› Состояния р3 и р4 одинаковы!")
else Console.WriteLine("-› Состояния р3 и р4 различны!");
}
Рис. 3.18. Результаты переопределения членов System.Object
Статические члены System.Object
В завершение нашего обсуждения базового класса .NET, находящегося на вершине иерархии классов, следует отметить, что System.Object определяет два статических члена (Object.Equals() и Object.ReferenceEquals()), обеспечивающих проверку на равенство значений и ссылок соответственно. Рассмотрим следующий программный код.
static void Main(string[] args) {
// Два объекта с идентичной конфигурацией.
Person р3 = new Person("Fred", "Jones", "222-22-2222", 98);
Person p4 = new Person("Fred", "Jones", "222-22-2222", 98);
// Одинаковы ли состояния р3 и р4? ИСТИНА!
Console.WriteLine("Одинаковы ли состояния: р3 и р4: {0} ", object.Equals(р3, р4));
// Являются ли они одним объектом в памяти? ЛОЖЬ!
Console.WriteLine ("Указывают ли р3 и р4 на один объект: {0} ", object.ReferenceEquals(р3, р4));
}
Исходный код. Проект ObjectMethods размещен в подкаталоге, соответствующем главе 3.
Типы данных System (и их обозначения в C#)
Вы, наверное, уже догадались, что каждый внутренний тип данных C# – это на самом деле сокращенное обозначение некоторого типа, определенного в пространстве имен System. В табл. 3.11 предлагается список типов данных System, указаны их диапазоны изменения, соответствующие им псевдонимы C# и информация о согласованности типа со спецификациями CLS.
Таблица 3.11. Типы System и их обозначения в C#
Обозначение в C# Согласованность с CLS Тип System Диапазон изменения Описание sbyte Нет System.SByte от -128 до 127 8-битовое число со знаком byte Да System.Byte От 0 до 255 8-битовое число без знака short Да System.Int16 от -32768 до 32767 16-битовое число со знаком ushort Нет System.UInt16 от 0 до 65535 16-битовое число без знака int Да System.Int32 от -2147483648 до 2147483647 32-битовое число со знаком Uint Нет System.UInt32 от 0 до 4294967295 32-битовое число без знака long Да System.Int64 от -9223372036854775808 до 9223372036854775807 64-битовое число со знаком ulong Нет System.UInt64 от 0 до 18446744073709551615 64-битовое число без знака char Да System.Char от U0000 до Uffff Отдельный 16-битовый символ Unicode float Да System.Single от 1.5×10-45 до 3.4×1038 32-битовое число с плавающим десятичным разделителем double Да System.Double от 5.0х10-324 до 1.7х10308 64-битовое число с плавающим десятичным разделителем bool Да System.Boolean true или false Представляет истину или ложь decimal Да System.Decimal от 100 до 1028 96-битовое число со знаком string Да System.String Ограничено системной памятью Представляет набор символов Unicode object Да System.Object Любой тип можно сохранить в объектной переменной Базовый класс всех типов во вселенной .NET
Замечание. По умолчанию действительный числовой литерал справа от операции присваивания интерпретируется, как double. Поэтому, чтобы инициализировать переменную типа float, используйте суффикс f или F (например 5.3F).