Возвращаясь к разговору о поверхностном (shallow) копировании, мы на примере, рассмотренном выше, убедились в том, что при использовании метода ToArray по умолчанию происходит именно этот тип копирования. Следующий фрагмент убедит вас в том, что и метод Clone, имеющийся в классе ArrayList, тоже поизводит "мелкое" копирование.
Что же надо сделать, чтобы создать настоящую копию коллекции (deep copy) и, таким образом, "отвязать" ее от коллекции-источника копирования? Документация говорит, что для этого надо в классе пользовательских объектов явно реализовать интерфейс ICloneable. Для нас это означает следующее. Надо добавить к списку родителей класса Man интерфейс ICloneable и, кроме того, ввести в класс явную реализацию метода Clone. Введем эти изменения.
class Man : IComparable, ICloneable // Теперь класс реализует два интерфейса
{
//===== Данные и методы класса Man . . .
object ICloneable.Clone() // Реализация метода интерфейса
{
return // Создаем и возвращаем новый объект в виде копии существующего (Ваш код . . .)
}
}
Убедитесь, что это работает с помощью следующего, несколько вычурного, кода:
Man ken = new Man("Ken Wood",40);
ArrayList men = new ArrayList(); // Создаем коллекцию
men.Add (ken); men.Add (ken);
ma = new Man[men.Count]; // Создаем новый массив заданного размера
for (int i=0; i<men.Count; i++) // Делаем копию, требующую много приведений типов
ma[i] = (Man)((ICloneable)(Man)men[i]).Clone();
Console.WriteLine("\nClone array of men:\n");
foreach (Man m in ma)
m.Out();
Console.WriteLine("\nChanging source men[0]");
men[0].In(); // Пытаемся изменить оригинал и проверить копию
Console.WriteLine("\nCheck the target: ma[0] = {0}", ma[0]);
Вы обратили внимание на двойное приведение типа? Именно такой стиль рекомендует документация Microsoft. Проверка показывает, что мы добились желаемого, но возможен и другой, более простой подход:
¨ Введите в класс Man конструктор копирования:
public Man (Man m) { name = m.name; age = m.age; }
Затем вместо того, чтобы переопределять непосредственно ICloneable.Clone:
object ICloneable.Clone() { . . . } // Так было ранее
¨ Дайте другую (hidden) версию (унаследованного от интерфейса ICloneable) метода Clone.
public object Clone()
{
return new Man(this);
}
Теперь клонирование всей коллекции выглядит несколько проще.
object[] mo = new Man[men.Count];
for (int i=0; i<men.Count; i++)// Делаем копию, требующую одно приведение типов
mo[i] = ((Man)men[i]).Clone();
Хотелось получить большего. Методы ToArray и Clone класса ArrayList не изменили своего поведения по отношению к классу Man, они по-прежнему делают мелкие копии ссылок. Введение конструктора копирования не изменило поведения этих методов, но делает возможным почленное копирование коллекции обычным способом, например,
object[] mo = new Man[men.Count]; int i =0;
foreach (Man m in men)
mo[i++] = new Man (m);
Этот класс иногда называют ассоциативным массивом. Он реализует функциональность коллекции пар, каждая из которых состоит из ключа (key) и ассоциированного с ним значения (value). Например, пара может состоять из имени и номера телефона: (string name, string phone). Здесь роль ключа выполняет строка символов name (объект класса string), а значением является другая строка — номер телефона.
Класс Hashtable является мощным инструментом, с помощью которого мы можем эффективно и надежно управлять сложными структурами данных. Для того, чтобы этот инструмент успешно работал, необходимо выполнить некоторые требования. Например, класс объектов, используемых в качестве ключа, должен реализовывать или просто наследовать методы: Object.GetHashCode и Object.Equals с учетом тех особенностей, которые были рассмотрены нами ранее.
Напомним, что двум равным объектам должны соответствовать одинаковые хеш-коды, которые возвращает метод GetHash класса Hashtable. По умолчанию он вызывает тот самый метод GetHashCode, который мы уже ввели в класс Man.
Оператор цикла foreach может быть использован для пробега по коллекции типа Hashtable. Этот оператор, как мы уже отмечали, самостоятельно осуществляет приведение типов (например, от типа object к типу Man) и, поэтому, он должен знать тип элементов в коллекции. Интересно, что в Hashtable хранятся пары, составляющие которой могут быть разных типов. Поэтому нельзя определенным образом ответить на вопрос какого типа элементы хранятся в Hashtable. Например, key может иметь тип ссылки на объект пользовательского класса, а value — любой другой тип. Для того, чтобы выйти из положения разработчики создали специальную структуру (тип элементов) DictionaryEntry. Для перебора элементов коллекции в цикле foreach вы должны использовать именно эту структуру.
foreach (DictionaryEntry e in myHashtable)
{
// Делаем что-то с каждым объектом e
}
Продемонстрируем как создать коллекцию пар такого типа и как ей управлять. Простейшим примером является коллекция пар (человек, профессия).
static void Main()
{
Man
ken = new Man("Ken Wood",40),
len = new Man("Lennie Tristano",50),
ben = new Man("Ben Webster",60);
Hashtable ht = new Hashtable();
ht.Add (ken, "Writer");
ht.Add (len, "Pianist");
ht.Add (ben, "Sax player");
foreach (DictionaryEntry e in ht)
Console.WriteLine( "{0} - {1}", e.Key, e.Value);
}
Этого кода достаточно для функционирования коллекции. Несмотря на то, что мы не давали никаких указаний относительно того, что будет храниться в коллекции, объект ht класса Hashtable прекрасно справился с той задачей, которую ему задали. Это можно объяснить тем, что на самом деле он хранит пары типа (адрес одного объекта, адрес другого объекта).
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.