Поскольку классы представляют ссылочные типы, то это накладывает некоторые ограничения на их использование. В частности:
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 предназначен для сравнения двух объектов 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
Добавим в этот же проект стандартные интерфейсы ICloneable и IComparable.Необходимо дать возможность сортировать квадраты по возрастанию стороны, а также дать возможность создавать копии кругов.
Создадим класс Point, содержащий координаты центра.
Создадим интерфейс IShape, который будут реализовать два класса - Circle (класс окружностей) и Square (класс квадратов).
Отработка