Разработка приложений на языке C#. Полезные настройки. Особые спецификаторы формата, страница 26

На примере, рассмотренном выше, вы убедились в том, что при использовании метода ToArray по умолчанию происходит поверхностное (shallow) копирование. Следующий фрагмент убедит вас в том, что и метод Clone, имеющийся в классе ArrayList, тоже поизводит мелкое копирование. Что же надо сделать, чтобы создать настоящую копию коллекции (deep copy) и, таким образом, отвязать ее от коллекции-источника копирования? Документация говорит, что для этого надо в классе пользовательских объектов явно реализовать интерфейс ICloneable. Для нас это означает следующее. Надо добавить к списку родителей класса Man интерфейс ICloneable и, кроме того, ввести в класс явную реализацию метода Clone. Сделайте это.

public class Man : IComparable, ICloneable // Теперь класс реализует два интерфейса

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 ("\nTest the target: ma[0] = {0}", ma[0]);

Вы обратили внимание на двойное приведение типа? Именно такой стиль работает при явной (explicit) реализации интерфейса. Проверка показывает, что мы добились желаемого, но возможен и другой, более простой подход:

·  Введите в класс Man конструктор копирования:

public Man (Man m) { name = m.name; age = m.age; }

Затем вместо того, чтобы переопределять непосредственно ICloneable.Clone:

object ICloneable.Clone() { . . . } // Так было ранее

·  Дайте другую (неявную, или implicit) версию (унаследованного от интерфейса 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();

Но это неблагодарный труд, так как методы CopyTo ToArray) не изменили своего поведения по отношению к классу Man, они по-прежнему делают копии ссылок. Введение конструктора копирования также не изменило поведения методов CopyTo и ToArray, но сделало возможным почленное копирование коллекции обычным способом.

object[] mo = new Man[men.Count];  int i =0;

foreach (Man m in men)

  mo[i++] = new Man (m);

Знание техники работы с интерфейсами, а также осведомленность о множестве существующих интерфейсов (уже реализованных классами библиотеки .NET) позволяет понять особенности функционирования новых видов коллекций. Все они реализуют тот или иной набор стандартных интерфейсов. Например, все массивы в .NET считаются наследниками класса System.Array:

public abstract class Array : ICloneable, IList, ICollection, IEnumerable

На самом деле мы не можем создать класс, производный от Array, но система гарантирует, что все методы, объявленные в нем, реализованы в массивах объектов любых типов. Именно по этой причине мы имели возможность пользоваться методами: GetLength, GetValue, CopyTo, а также свойствами: Rank, Length. Итак, массивы поддерживают идею наследования ООП, но не позволяют вмешиваться и переопределять методы и свойства. Это сделано с целью поддержки высокой эффективности работы с данными.

Класс Hashtable

Этот класс иногда называют ассоциативным массивом. Он реализует функциональность коллекции пар, каждая из которых состоит из ключа (key) и ассоциированного с ним значения (value). Например, пара может состоять из имени и номера телефона: (string name, string phone). Здесь роль ключа выполняет строка символов name (объект класса string), а значением является другая строка — номер телефона.