Оскільки класи представляють посилальні типи, то це накладає деякі обмеження на їх використання. Зокрема:
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; }
Більшість вбудованих в.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); } }
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
Додамо в цей же проект стандартні інтерфейси Icloneable і Icomparable.Необхідно дати можливість сортувати квадрати по зростанню сторони, а також дати можливість створювати копії кіл.
Створимо клас Point, що містить координати центру.
Створимо інтерфейс Ishape, який будє реалізувати два класи - Circle (клас кіл) і Square (клас квадратів).
Відпрацьовування