Чтобы проиллюстрировать стандартное поведение, обеспечиваемое базовым классом
Object
, создайте новый проект консольного приложения C# по имени
ObjectOverrides
.
Добавьте в проект новый файл класса С#, содержащий следующее пустое определение типа
Person
:
// Не забывайте, что класс Person расширяет Object.
class Person {}
Теперь обновите операторы верхнего уровня для взаимодействия с унаследованными членами
System.Object
:
Console.WriteLine("***** Fun with System.Object *****\n");
Person p1 = new Person();
// Использовать унаследованные члены System.Object.
Console.WriteLine("ToString: {0}", p1.ToString());
Console.WriteLine("Hash code: {0}", p1.GetHashCode());
Console.WriteLine("Type: {0}", p1.GetType());
// Создать другие ссылки на pi.
Person p2 = p1;
object o = p2;
// Указывают ли ссылки на один и тот же объект в памяти?
if (o.Equals(p1) && p2.Equals(o))
{
Console.WriteLine("Same instance!");
}
Console.ReadLine();
}
Вот вывод, получаемый в результате выполнения этого кода:
***** Fun with System.Object *****
ToString: ObjectOverrides.Person
Hash code: 58225482
Type: ObjectOverrides.Person
Same instance!
Обратите внимание на то, что стандартная реализация
ToString()
возвращает полностью заданное имя текущего типа (
ObjectOverrides.Person
). Как будет показано в главе 15, где исследуется построение специальных пространств имен, каждый проект C# определяет "корневое пространство имен", название которого совпадает с именем проекта. Здесь мы создали проект по имени
ObjectOverrides
, поэтому тип
Person
и класс
Program
помещены внутрь пространства имен
ObjectOverrides
.
Стандартное поведение метода
Equals()
заключается в проверке, указывают ли две переменные на один и тот же объект в памяти. В коде мы создаем новую переменную
Person
по имени
pi
. В этот момент новый объект
Person
помещается в управляемую кучу. Переменная
р2
также относится к типу
Person
. Тем не менее, вместо создания
нового экземпляра переменной
р2
присваивается ссылка
pi
. Таким образом, переменные
pi
и
р2
указывают на один и тот же объект в памяти, как и переменная
о
(типа
object
). Учитывая, что
pi
,
р2
и
о
указывают на одно и то же местоположение в памяти, проверка эквивалентности дает положительный результат.
Хотя готовое поведение
System.Object
в ряде случаев может удовлетворять всем потребностям, довольно часто в специальных типах часть этих унаследованных методов переопределяется. В целях иллюстрации модифицируем класс
Person
, добавив свойства, которые представляют имя, фамилию и возраст лица; все они могут быть установлены с помощью специального конструктора:
// Не забывайте, что класс Person расширяет Object.
class Person
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public int Age { get; set; }
public Person(string fName, string lName, int personAge)
{
FirstName = fName;
LastName = lName;
Age = personAge;
}
public Person(){}
}
Переопределение метода System.Object.ToString()
Многие создаваемые классы (и структуры) могут извлечь преимущества от переопределения метода
ToString()
для возвращения строки с текстовым представлением текущего состояния экземпляра типа. Помимо прочего это полезно при отладке. То, как вы решите конструировать результирующую строку — дело личных предпочтений; однако рекомендуемый подход предусматривает отделение пар "имя-значение" друг от друга двоеточиями и помещение всей строки в квадратные скобки (такому принципу следуют многие типы из библиотек базовых классов .NET Core). Взгляните на следующую переопределенную версию
ToString()
для класса
Person
:
public override string ToString()
=> $"[First Name: {FirstName}; Last Name: {LastName};
Age:
{Age}]";
Приведенная реализация метода
ToString()
довольно прямолинейна, потому что класс
Person
содержит всего три порции данных состояния. Тем не менее, всегда помните о том, что правильное переопределение
ToString()
должно также учитывать любые данные, определенные
выше в цепочке наследования.