Поскольку классы представляют ссылочные типы, то это накладывает некоторые ограничения на их использование. В частности:
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 (класс квадратов).
Отработка