Назад Уперед Зміст

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

Клонування об'єктів. Інтерфейс 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 призначено для порівняння двох об'єктів р1 і р2. Він також повертає три значення, залежно від результату порівняння: якщо перший об'єкт більше другого, то вертається число більше 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

Відпрацьовування

Назад Уперед Зміст