Интерфейс ICloneable
Вспомните из главы 6, что в классе
System.Object
определен метод по имени
MemberwiseClone()
, который применяется для получения
поверхностной (
неглубокой)
копии текущего объекта. Пользователи объекта не могут вызывать указанный метод напрямую, т.к. он является защищенным. Тем не менее, отдельный объект может самостоятельно вызывать
MemberwiseClone()
во время процесса
клонирования. В качестве примера создайте новый проект консольного приложения по имени
CloneablePoint
, в котором определен класс
Point
:
using System;
namespace CloneablePoint
{
// Класс по имени Point.
public class Point
{
public int X {get; set;}
public int Y {get; set;}
public Point(int xPos, int yPos) { X = xPos; Y = yPos;}
public Point(){}
// Переопределить Object.ToString().
public override string ToString() => $"X = {X}; Y = {Y}";
}
}
Учитывая имеющиеся у вас знания о ссылочных типах и типах значений (см.главу 4), должно быть понятно, что если вы присвоите одну переменную ссылочного типа другой такой переменной, то получите две ссылки, которые указывают на тот же самый объект в памяти. Таким образом, следующая операция присваивания в результате дает две ссылки на один и тот же объект
Point
в куче; модификация с использованием любой из ссылок оказывает воздействие на тот же самый объект в куче:
Console.WriteLine("***** Fun with Object Cloning *****\n");
// Две ссылки на один и тот же объект!
Point p1 = new Point(50, 50);
Point p2 = p1;
p2.X = 0;
Console.WriteLine(p1);
Console.WriteLine(p2);
Console.ReadLine();
Чтобы предоставить специальному типу возможность возвращения вызывающему коду идентичную копию самого себя, можно реализовать стандартный интерфейс
ICloneable
. Как было показано в начале главы, в интерфейсе
ICloneable
определен единственный метод по имени
Clone()
:
public interface ICloneable
{
object Clone();
}
Очевидно, что реализация метода
Clone()
варьируется от класса к классу. Однако базовая функциональность в основном остается неизменной: копирование значений переменных-членов в новый объект того же самого типа и возвращение его пользователю. В целях демонстрации модифицируйте класс
Point
:
// Теперь Point поддерживает способность клонирования.
public class Point : ICloneable
{
public int X { get; set; }
public int Y { get; set; }
public Point(int xPos, int yPos) { X = xPos; Y = yPos; }
public Point() { }
// Переопределить Object.ToString().
public override string ToString() => $"X = {X}; Y = {Y}";
// Возвратить копию текущего объекта.
public object Clone() => new Point(this.X, this.Y);
}
Теперь можно создавать точные автономные копии объектов типа
Point
:
Console.WriteLine("***** Fun with Object Cloning *****\n");
...
// Обратите внимание, что Clone() возвращает простой тип object
.
// Для получения производного типа требуется явное приведение
Point p3 = new Point(100, 100);
Point p4 = (Point)p3.Clone();
// Изменить р4.Х (что не приводит к изменению р3.Х).
p4.X = 0;
// Вывести все объекты.
Console.WriteLine(p3);
Console.WriteLine(p4);
Console.ReadLine();
Несмотря на то что текущая реализация типа
Point
удовлетворяет всем требованиям, есть возможность ее немного улучшить. Поскольку
Point
не содержит никаких внутренних переменных ссылочного типа, реализацию метода
Clone()
можно упростить:
// Копировать все поля Point по очереди.
public object Clone() => this.MemberwiseClone();
Тем не менее, учтите, что если бы в типе
Point
содержались любые переменные-члены ссылочного типа, то метод
MemberwiseClone()
копировал бы ссылки на эти объекты (т.е. создавал бы
поверхностную копию). Для поддержки подлинной
глубокой (
детальной)
копии во время процесса клонирования понадобится создавать новые экземпляры каждой переменной-члена ссылочного типа. Давайте рассмотрим пример.
Более сложный пример клонирования
Теперь предположим, что класс
Point
содержит переменную-член ссылочного типа
PointDescription
. Данный класс представляет дружественное имя точки, а также ее идентификационный номер, выраженный как
System.Guid
(глобально уникальный идентификатор (globally unique identifier — GUID), т.е. статистически уникальное 128-битное число). Вот как выглядит реализация: