Содержание
Абсолютно все классы в .NET и C#, в частности, включая и те, которые мы создаем самостоятельно, а равно и базовые типы данных, такие как System.Int32
, System.Double
и так далее, неявно являются наследниками класса Object
. В любом случае, даже если при создании своего класса мы не указываем класс Object
в качестве родительского, то неявно по умолчанию класс Object
стоит на вершине иерархии наследования. Исходя из этого, все типы данных и классы могут реализовать методы, определенные в классе System.Object
. Рассмотрим методы класса Object
более подробно.
Метод ToString в C#
Метод ToString()
возвращает строковое представление объекта. Так, для базовых типов метод вернет их строковое значение, например:
int num = 7; Console.WriteLine(num.ToString()); // выведет число 7 double d = 3.5; Console.WriteLine(d.ToString()); // выведет число 3,5
В случае использования метода ToString()
для классов, метод выведет полное название класса с указанием пространства имен, в котором этот класс был определен. Метод является виртуальным (virtual
), поэтому мы можем его переопределить. Например:
class Clock { public int Hours { get; set; } public int Minutes { get; set; } public int Seconds { get; set; } public override string ToString() { return $"{Hours}:{Minutes}:{Seconds}"; } } class Program { private static void Main(string[] args) { Clock clock = new Clock { Hours = 15, Minutes = 34, Seconds = 53 }; Console.WriteLine(clock.ToString()); // выведет 15:34:53 Console.Read(); } }
В представленном примере в классе Clock
метод ToString()
переопределен, поэтому в консоль будет выведено не название класса, а время, которое мы задаем при инициализации объекта. При этом, мы можем, как обычно при переопределении методов, пользоваться как собственной реализацией метода ToString()
в класса-наследнике, так и базовой версией, используя ключевое слово base
. Например:
class Person { public string Name { get; set; } public override string ToString() { if (String.IsNullOrEmpty(Name)) return base.ToString(); return Name; } }
Если свойство Name не будет задано, то в результате вызова метода ToString()
будет использована базовая версия и метод вернет имя класса. Для проверки свойства на пустоту при этом используется метод строк IsNullOrEmpty()
.
Различные технологии на платформе .NET активно используют метод ToString для разных целей. В частности, тот же метод Console.WriteLine()
по умолчанию выводит именно строковое представление объекта. Поэтому, если нам надо вывести строковое представление объекта на консоль, то при передаче объекта в метод Console.WriteLine необязательно использовать метод ToString()
— он вызывается неявно:
Метод GetHashCode в C#
Метод GetHashCode()
позволяет возвратить некоторое числовое значение, соответствующее объекту или, как ещё говорят, его хэш-код. По этому числу можно, например, сравнивать объекты между собой, сортировать их и т.д.. Мы можем определять различные алгоритмы генерации хэш-кода или же взять реализацию из базового типа:
class Person { public string Name { get; set; } public override int GetHashCode() { return Name.GetHashCode(); } }
В приведенном выше примере метод GetHashCode()
возвращает хеш-код для свойства Name
. Поэтому, если два объекта Person
будут иметь одно и то же значение свойства Name
, то они будут возвращать один и тот же хеш-код.
Метод Equals в C#
По умолчанию Equals() проверяет два объекта на равенство, используя для этого ссылки на объект. Однако, мы можем переопределить метод Equals()
, что позволяет проверить два объекта на равенство, используя собственный алгоритм сравнения. Например:
class Person { public string Name { get; set; } public override bool Equals(object obj) { if (obj.GetType() != this.GetType()) return false; Person person = (Person)obj; return (this.Name == person.Name); } }
Метод Equals()
принимает в качестве параметра объект любого типа, который мы приводим к текущему, если они являются объектами одного класса. Затем сравниваем по именам. Если имена равны, возвращаем true
, что будет говорить, что объекты равны. Однако при необходимости реализацию метода можно сделать более сложной, например, сравнивать по нескольким свойствам при их наличии.
Применение метода:
Person person1 = new Person { Name = "Tom" }; Person person2 = new Person { Name = "Bob" }; Person person3 = new Person { Name = "Tom" }; bool p1Ep2 = person1.Equals(person2); // false bool p1Ep3 = person1.Equals(person3); // true
Если необходимо сравнивать два сложных объекта, как в примере выше, то лучше использовать метод Equals()
, а не стандартную операцию ==
.
Метод ReferenceEquals в C#
В отличие от Equals()
, этот метод не переопределяется и использует для сравнения двух объектов, используя ссылки на эти объекты.
Метод GetType и получение типа объекта в C#
Метод GetType()
позволяет получить тип объекта. Например:
Person person = new Person { Name = "Tom" }; Console.WriteLine(person.GetType()); // Person
Метод возвращает объект Type
(тип объекта). С помощью ключевого слова typeof
мы получаем тип класса и сравниваем его с типом объекта. И если этот объект представляет тип Person
, то выполняем определенные действия.
object person = new Person { Name = "Tom" }; if (person.GetType() == typeof(Person)) Console.WriteLine("Это реально класс Person");
Причем поскольку класс Object
является базовым типом для всех классов, то мы можем переменной типа object
присвоить объект любого типа. Однако для этой переменной метод GetType()
все равно вернет тот тип, на объект которого ссылается переменная. То есть в данном случае объект типа Person
.
В отличие от методов ToString()
, Equals()
, GetHashCode()
метод GetType()
не виртуальные и не может быть переопределен.
Отличие методов Equals() и ReferenceEquals()
Следует отметить некоторые различия в методах Equals()
и ReferenceEquals()
.
Во-первых, несмотря на то, что оба этих метода по умолчанию действуют одинаково — сравнивают два объекта по ссылкам, тем не менее, так как Equals()
может переопределяться в наследниках, то этот метод часто используется в классах-наследниках для сравнения двух объектов именно по значению полей, как было показано выше.
Во-вторых, механизм работы Equals()
и ReferenceEquals()
различается в зависимости от типа данных. Это различие наглядно показано в следующем примере:
static void Main(string[] args) { int i = 2; Console.WriteLine(Object.Equals(i, i));//True Console.WriteLine(Object.ReferenceEquals(i, i));//False }
Здесь мы сравнили число типа int
само с собой и ReferenceEquals()
почему-то выдал результат False
. Происходит это по следующей причине: при сравнении типов значений. они упаковываются перед передачей в ReferenceEquals()
и, в итоге, сравниваются не сами значения, а ссылки на полученные объекты, которые оказываются различными. Соответственно, Equals()
действует иначе — если текущий экземпляр является типом значения, то Equals()
проверяет равенство значений. Равенство значений означает, что два объекта имеют один и тот же тип. То есть, в следующем примере два целых числа не будут равны при использовании Equals()
так как они относятся к разным типам данных:
int i = 2; byte b = 2; Console.WriteLine(Object.Equals(i, b));//False
Что использовать для сравнения — Equals()
, ReferenceEquals()
или ==
зависит от конкретных задач, но можно предложить следующее:
- для типов значений:
- если требуется сравнивать значения без учёта типа данных, то используем
==
- если требуется сравнивать значения с учётом типа данных — используем
Equals()
- если требуется сравнивать значения без учёта типа данных, то используем
- для ссылочных типов:
- если необходимо определить равенство двух объектов по значениям его полей — используем переопределенную версию
Equals()
- если необходимо определить то, что две переменные ссылочного типа ссылаются на один и тот же объект в памяти — используем
ReferenceEquals()
- если необходимо определить равенство двух объектов по значениям его полей — используем переопределенную версию
Итого
Сегодня мы рассмотрели основные методы класса Object
— родителя для всех классов в C#. Часть методов этого класса являются виртуальными, что позволяет их переопределять в классах-наследниках и реализовать, например, свою логику сравнения двух объектов. Также рассмотрены различия двух похожих методов Equals()
и ReferenceEquals()
и предложен алгоритм использования того или иного метода в зависимости от типа данных переменной.