Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
// Копировать все поля Point по очереди.
public object Clone() => this.MemberwiseClone();
Тем не менее, учтите, что если бы в типе Point содержались любые переменные-члены ссылочного типа, то метод MemberwiseClone() копировал бы ссылки на эти объекты (т.е. создавал бы поверхностную копию). Для поддержки подлинной глубокой (детальной) копии во время процесса клонирования понадобится создавать новые экземпляры каждой переменной-члена ссылочного типа. Давайте рассмотрим пример.
Более сложный пример клонирования
Теперь предположим, что класс Point содержит переменную-член ссылочного типа PointDescription. Данный класс представляет дружественное имя точки, а также ее идентификационный номер, выраженный как System.Guid (глобально уникальный идентификатор (globally unique identifier — GUID), т.е. статистически уникальное 128-битное число). Вот как выглядит реализация:
using System;
namespace CloneablePoint
{
// Этот класс описывает точку.
public class PointDescription
{
public string PetName {get; set;}
public Guid PointID {get; set;}
public PointDescription()
{
PetName = "No-name";
PointID = Guid.NewGuid();
}
}
}
Начальные изменения самого класса Point включают модификацию метода ToString() для учета новых данных состояния, а также определение и создание ссылочного типа PointDescription. Чтобы позволить внешнему миру устанавливать дружественное имя для Point, необходимо также изменить аргументы, передаваемые перегруженному конструктору:
public class Point : ICloneable
{
public int X { get; set; }
public int Y { get; set; }
public PointDescription desc = new PointDescription();
public Point(int xPos, int yPos, string petName)
{
X = xPos; Y = yPos;
desc.PetName = petName;
}
public Point(int xPos, int yPos)
{
X = xPos; Y = yPos;
}
public Point() { }
// Переопределить Object.ToString().
public override string ToString()
=> $"X = {X}; Y = {Y}; Name = {desc.PetName};nID = {desc.PointID}n";
// Возвратить копию текущего объекта.
public object Clone() => this.MemberwiseClone();
}
Обратите внимание, что метод Clone() пока еще не обновлялся. Следовательно, когда пользователь объекта запросит клонирование с применением текущей реализации, будет создана поверхностная (почленная) копия. В целях иллюстрации модифицируйте вызывающий код, как показано ниже:
Console.WriteLine("***** Fun with Object Cloning *****n");
...
Console.WriteLine("Cloned p3 and stored new Point in p4");
Point p3 = new Point(100, 100, "Jane");
Point p4 = (Point)p3.Clone();
Console.WriteLine("Before modification:"); // Перед модификацией
Console.WriteLine("p3: {0}", p3);
Console.WriteLine("p4: {0}", p4);
p4.desc.PetName = "My new Point";
p4.X = 9;
Console.WriteLine("nChanged p4.desc.petName and p4.X");
Console.WriteLine("After modification:"); // После модификации
Console.WriteLine("p3: {0}", p3);
Console.WriteLine("p4: {0}", p4);
Console.ReadLine();
В приведенном далее выводе видно, что хотя типы значений действительно были изменены, внутренние ссылочные типы поддерживают одни и те же значения, т.к. они "указывают" на те же самые объекты в памяти (в частности, оба объекта имеют дружественное имя Му new Point):
***** Fun with Object Cloning *****
Cloned p3 and stored new Point in p4
Before modification:
p3: X = 100; Y = 100; Name = Jane;
ID = 133d66a7-0837-4bd7-95c6-b22ab0434509
p4: X = 100; Y = 100; Name = Jane;
ID = 133d66a7-0837-4bd7-95c6-b22ab0434509
Changed p4.desc.petName and p4.X
After modification:
p3: X = 100; Y = 100; Name = My new Point;
ID = 133d66a7-0837-4bd7-95c6-b22ab0434509
p4: X = 9; Y = 100; Name = My new Point;
ID = 133d66a7-0837-4bd7-95c6-b22ab0434509
Чтобы заставить метод Clone() создавать полную глубокую копию внутренних ссылочных типов, нужно сконфигурировать объект, возвращаемый методом MemberwiseClone(), для учета имени текущего объекта Point (тип System.Guid на самом деле является структурой, так что числовые данные будут действительно копироваться). Вот одна из возможных реализаций:
// Теперь необходимо скорректировать код для учета члена.
public object Clone()
{
// Сначала получить поверхностную копию.
Point newPoint = (Point)this.MemberwiseClone();
// Затем восполнить пробелы.
PointDescription currentDesc = new PointDescription();
currentDesc.PetName = this.desc.PetName;
newPoint.desc = currentDesc;
return newPoint;
}
Если снова запустить приложение и просмотреть его вывод (показанный далее), то будет видно, что возвращаемый методом Clone() объект Point действительно копирует свои внутренние переменные-члены ссылочного типа (обратите внимание, что дружественные имена у рЗ и р4 теперь уникальны):
***** Fun with Object Cloning *****
Cloned p3 and stored new Point in p4
Before modification:
p3: X = 100; Y = 100; Name = Jane;
ID = 51f64f25-4b0e-47ac-ba35-37d263496406
p4: X = 100; Y = 100; Name = Jane;
ID = 0d3776b3-b159-490d-b022-7f3f60788e8a
Changed p4.desc.petName and p4.X
After modification:
p3: X = 100; Y = 100; Name = Jane;
ID = 51f64f25-4b0e-47ac-ba35-37d263496406
p4: X = 9; Y = 100; Name = My new Point;
ID = 0d3776b3-b159-490d-b022-7f3f60788e8a
Давайте подведем итоги по процессу клонирования. При наличии класса или структуры, которая содержит только типы значений, необходимо реализовать метод Clone() с использованием метода MemberwiseClone(). Однако если есть специальный тип, поддерживающий ссылочные типы, тогда для построения глубокой копии может потребоваться создать новый объект, который учитывает каждую переменную-член ссылочного типа.
Интерфейс IComparable
Интерфейс System.IComparable описывает поведение, которое позволяет сортировать объекты на основе указанного ключа. Вот его формальное определение:
// Данный интерфейс позволяет объекту указывать
// его отношение с другими подобными объектами
public interface IComparable
{
int CompareTo(object o);
}
На заметку! Обобщенная версия этого интерфейса (IСоmраrаble<Т>) предлагает более безопасный в отношении типов способ обработки операций сравнения объектов. Обобщения исследуются в главе 10.
Создайте новый проект консольного