Добавить в цитаты Настройки чтения

Страница 318 из 371

Как правило, в классе вначале определяют метод CompareTo, а после этого вводят перегруженные операции, чтобы выполнять сравнение объектов привычным образом с использованием знаков операций отношения.

Давайте введем отношение порядка на классе Person, рассмотренном в лекции 16, сделав этот класс наследником интерфейса IComparable. Реализуем в этом классе метод интерфейса CompareTo:

public class Person: IComparable

{

    public int CompareTo(object pers)

    {

         const string s = "Сравниваемый объект не принадлежит классу Person";

         Person р = pers as Person;

         if (!p.Equals(null))

         return (fam.CompareTo(p.fam));

         throw new ArgumentException (s);

    }

    // другие компоненты класса

}

Поскольку аргумент метода должен иметь универсальный тип object, то перед выполнением сравнения его нужно привести к типу Person. Это приведение использует операцию as, позволяющую проверить корректность выполнения приведения.

При приведении типов часто используются операции is и as. Логическое выражение (obj is T) истинно, если объект obj имеет тип T. Оператор присваивания (obj = р as T;) присваивает объекту obj объект P, приведенный к типу T, если такое приведение возможно, иначе объекту присваивается значение null. Семантику as можно выразить следующим условным выражением: (P is T)? (T) P: (Т)null.

Заметьте также, что при проверке на значение null используется отношение Equals, а не обычное равенство, которое будет переопределено.

Отношение порядка на объектах класса Person задается как отношение порядка на фамилиях персон. Так как строки наследуют интерфейс IComparable, то для фамилий персон вызывается метод CompareTo, его результат и возвращается в качестве результата метода CompareTo для персон. Если аргумент метода не будет соответствовать нужному типу, то выбрасывается исключение со специальным уведомлением.

Конечно, сравнение персон может выполняться по разным критериям: возрасту, росту, зарплате. Общий подход к сравнению персон будет рассмотрен в следующей лекции 20.

Введем теперь в нашем классе Person перегрузку операций отношения:

public static bool operator <(Person p1, Person p2)

{

    return (pi.CompareTo(p2) < 0);

}

public static bool operator >(Person p1, Person p2)

{

    return (pi.CompareTo(p2) > 0);

}

public static bool operator <=(Person p1, Person p2)

{

    return (pi.CompareTo(p2) <= 0);

}

public static bool operator >= (Person p1, Person p2)

{

    return (pi.CompareTo(p2) >=0);

}

public static bool operator == (Person p1, Person p2)

{

    return (pi.CompareTo(p2) == 0);

}

public static bool operator!= (Person p1, Person p2)

{

    return (p1.CompareTo(p2)!= 0);

}

Как обычно, приведу тестовый пример, проверяющий работу с введенными методами:

public void TestCompare()

{

    Person poet1 = new Person("Пушкин");





    Person poet2 = new Person("Лермонтов");

    Person poet3 = new Person("Пастернак");

    Person poet4 = new Person("Мандельштам");

    Person poet5 = new Person("Ахматова");

    Person poet6 = new Person("Цветаева");

    Console.WriteLine("{0} > {1} = {2}", poet1.Fam,

         poet2.Fam, (poet1 > poet2));

    Console.WriteLine("{0} >= {1} = {2}", poet3.Fam,

         poet4.Fam, (poet3 >= poet4));

    Console.WriteLine("{0}!= {1} = {2}", poet5.Fam,

         poet6.Fam, (poet5!= poet6));

}

Вот результаты работы этого теста.

Рис. 19.4. Сравнение персон

Конечно, заданный нами порядок не имеет никакого отношения к поэтическому дару, а лишь говорит об относительном расположении фамилий поэтов в словарях.

Клонирование и интерфейс ICloneable

Клонированием называется процесс создания копии объекта, а копия объекта называется его клоном. Различают два типа клонирования: поверхностное (shallow) и глубокое (deep). При поверхностном клонировании копируется сам объект. Все значимые поля клона получают значения, совпадающие со значениями полей объекта; все ссылочные поля клона являются ссылками на те же объекты, на которые ссылается и сам объект. При глубоком клонировании копируется вся совокупность объектов, связанных взаимными ссылками. Представьте себе мир объектов, описывающих людей. У этих объектов могут быть ссылки на детей и родителей, учителей и учеников, друзей и родственников. В текущий момент может существовать большое число таких объектов, связанных ссылками. Достаточно выбрать один из них в качестве корня, и при его клонировании воссоздастся копия существующей структуры объектов.

Глубокое клонирование требует рекурсивной процедуры обхода существующей структуры объектов, тщательно отработанной во избежание проблемы зацикливания. В общем случае, когда есть несколько классов, являющихся взаимными клиентами, глубокое клонирование требует наличия в каждом классе рекурсивной процедуры. Эти процедуры взаимно вызывают друг друга. Я не буду в этой лекции приводить примеры, хотя среди проектов, поддерживающих книгу, есть и проект, реализующий глубокое клонирование, где объекты разных классов связаны взаимными ссылками.

Поверхностное клонирование можно выполнить достаточно просто. Наиболее простой путь — клонирование путем вызова метода MemberwiseClone, наследуемого от прародителя object. Единственное, что нужно помнить: этот метод защищен, он не может быть вызван у клиента класса. Поэтому клонирование нужно выполнять в исходном классе, используя прием обертывания метода.

Давайте обеспечим эту возможность для класса Person, создав в нем соответствующий метод:

public Person StandartClone()

{

    Person p = (Person)this.MemberwiseClone ();

    return(p);

}

Теперь клиенты класса могут легко создавать поверхностные клоны. Вот пример:

public void TestStandartClone()

{

    Person mother = new Person("Петрова Анна");

    Person daughter = new Person ("Петрова Ольга");

    Person son = new Person("Петров Игорь");

    mother[0] = daughter;

    mother[1] = son;

    Person mother_clone = mother.StandartClone();

    Console.WriteLine("Дети матери: {0}",mother.Fam);

    Console.WriteLine (mother[0].Fam);

    Console.WriteLine (mother[1].Fam);

    Console.WriteLine("Дети клона: {0}",mother_clone.Fam);

    Console.WriteLine (mother_clone[0].Fam);

    Console.WriteLine (mother_clone[1].Fam);

}

При создании клона будет создана копия только одного объекта mother. Обратите внимание: при работе с полем children, задающим детей, используется индексатор класса Person, выполняющий индексацию по этому полю. Вот как выглядят результаты работы теста.

Рис. 19.5. Поверхностное клонирование

Если стандартное поверхностное клонирование нас не устраивает, то класс можно объявить наследником интерфейса ICloneabie и реализовать метод Clone — единственный метод этого интерфейса. В нем можно реализовать полное глубокое клонирование или подходящую для данного случая модификацию.