Исследование MS Visual С#, страница 15

Реализуем интерфейс ICloneable

Возвращаясь к разговору о поверхностном (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);

Класс Hashtable

Этот класс иногда называют ассоциативным массивом. Он реализует функциональность коллекции пар, каждая из которых состоит из ключа (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 прекрасно справился с той задачей, которую ему задали. Это можно объяснить тем, что на самом деле он хранит пары типа (адрес одного объекта, адрес другого объекта).