Назад Вперед Содержание

Стандартные интерфейсы

Клонирование объектов. Интерфейс ICloneable

Поскольку классы представляют ссылочные типы, то это накладывает некоторые ограничения на их использование. В частности:

class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person { Name="Tom", Age = 23 };
        Person p2 = p1;
        p2.Name = "Alice";
        Console.WriteLine(p1.Name); // Alice
 
        Console.Read();
    }
}
 
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

В данном случае объекты p1 и p2 будут указывать на один и тот же объект в памяти, поэтому изменения свойств в переменной p2 затронут также и переменную p1. Чтобы переменная p2 указывала на новый объект, но со значениями из p1, мы можем применить клонирование с помощью реализации интерфейса ICloneable:


public interface ICloneable
{
    object Clone();
}

Реализация интерфейса в классе Person могла бы выглядеть следующим образом:

class Person : ICloneable
{
    public string Name { get; set; }
    public int Age { get; set; }
    public object Clone()
    {
        return new Person { Name = this.Name, Age = this.Age };
    }
}

Использование:

class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person { Name="Tom", Age = 23 };
        Person p2 = (Person)p1.Clone();
        p2.Name = "Alice";
        Console.WriteLine(p1.Name); // Tom
 
        Console.Read();
    }
}

Теперь все нормально копируется, изменения в свойствах p2 не сказываются на свойствах в p1. Для сокращения кода копирования мы можем использовать специальный метод MemberwiseClone(), который возвращает копию объекта:

class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public object Clone() { return this.MemberwiseClone(); } }

Этот метод реализует поверхностное (неглубокое) копирование. Однако данного копирования может быть недостаточно. Например, пусть класс Person содержит ссылку на объект Company:

class Person : ICloneable
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Company Work { get; set; }
 
    public object Clone()
    {
            return this.MemberwiseClone();
    }
}
 
class Company
{
    public string Name { get; set; }
}

В этом случае при копировании новая копия будет указывать на тот же объект Company:

Person p1 = new Person { Name="Tom", Age = 23, Work= new Company { Name = "Microsoft" } };
Person p2 = (Person)p1.Clone();
p2.Work.Name = "Google";
p2.Name = "Alice";
Console.WriteLine(p1.Name); // Tom
Console.WriteLine(p1.Work.Name); // Google - а должно быть Microsoft

Поверхностное копирование работает только для свойств, представляющих примитивные типы, но не для сложных объектов. И в этом случае надо применять глубокое копирование:

class Person : ICloneable
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Company Work { get; set; }
 
    public object Clone()
    {
        Company company = new Company { Name = this.Work.Name };
        return new Person
        {
            Name = this.Name,
            Age = this.Age,
            Work = company
        };
    }
}
 
class Company
{
    public string Name { get; set; }

Сортировка объектов. Интерфейс IComparable

Большинство встроенных в .NET классов коллекций и массивы поддерживают сортировку. С помощью одного метода, который, как правило, называется Sort() можно сразу отсортировать по возрастанию весь набор данных. Например:

int[] numbers = new int[] { 97, 45, 32, 65, 83, 23, 15 };
Array.Sort(numbers);
foreach (int n in numbers)
    Console.WriteLine(n);

Однако метод Sort по умолчанию работает только для наборов примитивных типов, как int или string. Для сортировки наборов сложных объектов применяется интерфейс IComparable. Он имеет всего один метод:

public interface IComparable
{
    int CompareTo(object o);
}

Метод CompareTo предназначен для сравнения текущего объекта с объектом, который передается в качестве параметра object o. На выходе он возвращает целое число, которое может иметь одно из трех значений:

Например, имеется класс Person:

class Person : IComparable
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int CompareTo(object o)
    {
        Person p = o as Person;
        if (p != null)
            return this.Name.CompareTo(p.Name);
        else
            throw new Exception("Невозможно сравнить два объекта");
    }
}

Здесь в качестве критерия сравнения выбрано свойство Name объекта Person. Поэтому при сравнении здесь фактически идет сравнение значения свойства Name текущего объекта и свойства Name объекта, переданного через параметр. Если вдруг объект не удастся привести к типу Person, то выбрасывается исключение.

Применение:

Person p1 = new Person { Name = "Bill", Age = 34 };
Person p2 = new Person { Name = "Tom", Age = 23 };
Person p3 = new Person { Name = "Alice", Age = 21 };
 
Person[] people = new Person[] { p1, p2, p3 };
Array.Sort(people);
 
foreach(Person p in people)
{
    Console.WriteLine("{0} - {1}", p.Name, p.Age);
}

Интерфейс IComparable имеет обобщенную версию, поэтому мы могли бы сократить и упростить его применение в классе Person:

class Person : IComparable
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int CompareTo(Person p)
    {
        return this.Name.CompareTo(p.Name);
    }
}

Применение компаратора

Кроме интерфейса IComparable платформа .NET также предоставляет интерфейс IComparer:

interface IComparer
{
    int Compare(object o1, object o2);
}

Метод Compare предназначен для сравнения двух объектов o1 и o2. Он также возвращает три значения, в зависимости от результата сравнения: если первый объект больше второго, то возвращается число больше 0, если меньше - то число меньше нуля; если оба объекта равны, возвращается ноль.

Создадим компаратор объектов Person. Пусть он сравнивает объекты в зависимости от длины строки - значения свойства Name:

class PeopleComparer : IComparer
{
    public int Compare(Person p1, Person p2)
    {
        if (p1.Name.Length > p2.Name.Length)
            return 1;
        else if (p1.Name.Length < p2.Name.Length)
            return -1;
        else
            return 0;
    }
}

В данном случае используется обобщенная версия интерфейса IComparer, чтобы не делать излишних преобразований типов. Применение компаратора:

Person p1 = new Person { Name = "Bill", Age = 34 };
Person p2 = new Person { Name = "Tom", Age = 23 };
Person p3 = new Person { Name = "Alice", Age = 21 };
 
Person[] people = new Person[] { p1, p2, p3 };
Array.Sort(people, new PeopleComparer());
 
foreach(Person p in people)
{
    Console.WriteLine("{0} - {1}", p.Name, p.Age);
}

Объект компаратора указывается в качестве второго параметра метода Array.Sort(). При этом не важно, реализует ли класс Person интерфейс IComparable или нет. Правила сортировки, установленные компаратором, будут иметь больший приоритет. В начале будут идти объекты Person, у которых имена меньше, а в конце - у которых имена длиннее:

Tom - 23
Bill - 34
Alice - 21

Пример из раздела 5.1

Добавим в этот же проект стандартные интерфейсы ICloneable и IComparable.Необходимо дать возможность сортировать квадраты по возрастанию стороны, а также дать возможность создавать копии кругов.

Создадим класс Point, содержащий координаты центра.

Создадим интерфейс IShape, который будут реализовать два класса - Circle (класс окружностей) и Square (класс квадратов).

В интерфейсе Shape объявим следующие члены интерфейса:

В дочернем классе Square:

В дочернем классе Circle:

В классе Program

Отработка

Назад Вперед Содержание