Давайте расширим наш класс, придав ему родительский интерфейс ICloneabie. Реализация метода clone будет отличаться от стандартной реализации тем, что к имени объекта — полю Fam — будет приписываться слово "clone". Вот как выглядит этот метод:
public object Clone()
{
Person р = new Person(this.fam + "_clone");
//копирование полей
p.age = this.age; p.children = this.children;
p.count_children = this.count_children;
p.health = this.health; p.salary = this.salary;
p.status = this.status;
return (p);
}
Эта реализация является слегка модифицированной версией стандартного поверхностного клонирования. Я добавил несколько строчек в тестирующую процедуру для проверки работы этой версии клона:
Person mother_clone2 = (Person)mother.Clone();
Console.WriteLine("Дети клона_2: {0}",mother_clone2.Fam);
Console.WriteLine (mother_clone2[0].Fam);
Console.WriteLine (mother_clone2[1].Fam);
Все работает должным образом.
Сериализация объектов
При работе с программной системой зачастую возникает необходимость в сериализации объектов. Под сериализацией понимают процесс сохранения объектов в долговременной памяти (файлах) в период выполнения системы. Под десериализацией понимают обратный процесс — восстановление состояния объектов, хранимых в долговременной памяти. Механизмы сериализации C# и Framework.Net поддерживают два формата сохранения данных — в бинарном файле и XML-файле. В первом случае данные при сериализации преобразуются в бинарный поток символов, который при десериализации автоматически преобразуется в нужное состояние объектов. Другой возможный преобразователь (SOAP formatter) запоминает состояние объекта в формате xml.
Сериализация позволяет запомнить рубежные состояния системы объектов с возможностью последующего возвращения к этим состояниям. Она необходима, когда завершение сеанса работы не означает завершение вычислений. В этом случае очередной сеанс работы начинается с восстановления состояния, сохраненного в конце предыдущего сеанса работы. Альтернативой сериализации является работа с обычной файловой системой, с базами данных и другими хранилищами данных. Поскольку механизмы сериализации, предоставляемые языком С#, эффективно поддерживаются. Net Framework, то при необходимости сохранения данных значительно проще и эффективнее пользоваться сериализацией, чем самому организовывать их хранение и восстановление.
Еще одно важное применение сериализации — это обмен данными удаленных систем. При удаленном обмене данными предпочтительнее формат xml из-за открытого стандарта передачи данных в Интернете по soap-протоколу, из-за открытого стандарта на структуру xml-документов. Обмен становится достаточно простым даже для систем, построенных на разных платформах и в разных средах разработки.
Так же, как и клонирование, сериализация может быть поверхностной, когда сериализуется на одном шаге единственный объект, и глубокой, когда, начиная с корневого объекта, сериализуется совокупность объектов, связанных взаимными ссылками (граф объектов). Глубокую сериализацию, часто обязательную, самому организовать непросто, так как она требует, как правило, рекурсивного обхода структуры объектов.
Если класс объявить с атрибутом [Serializable], то в него встраивается стандартный механизм сериализации, поддерживающий, что крайне приятно, глубокую сериализацию. Если по каким-либо причинам стандартная сериализация нас не устраивает, то класс следует объявить наследником интерфейса ISseriaizabie, реализация методов которого позволит управлять процессом сериализации. Мы рассмотрим обе эти возможности.
Класс с атрибутом сериализации
Класс, объекты которого предполагается сериализовать стандартным образом, должен при объявлении сопровождаться атрибутом [Serializable]. Стандартная сериализация предполагает два способа сохранения объекта: в виде бинарного потока символов и в виде xml-документа. В бинарном потоке сохраняются все поля объекта, как открытые, так и закрытые. Процессом этим можно управлять, помечая некоторые поля класса атрибутом [Nonserialized] — эти поля сохраняться не будут:
[Serializable] public class Test
{
public string name;
[NonSerialized] int id; int age;
//другие поля и методы класса
}
В класс Test встроен стандартный механизм сериализации его объектов. При сериализации поля name и аде будут сохраняться, поле id — нет.
Для запуска механизма необходимо создать объект, называемый форматером и выполняющий сериализацию и десериализацию данных с подходящим их форматированием. Библиотека FCL предоставляет два класса форматеров. Бинарный форматер, направляющий данные в бинарный поток, принадлежит классу BinaryFormatter. Этот класс находится в пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Binary
Давайте разберемся, как устроен этот класс. Он является наследником двух интерфейсов: iFormatter и IRemotingFormatter. Интерфейс IFormatter имеет два открытых метода: Serialize и Deserialize, позволяющих сохранять и восстанавливать всю совокупность связанных объектов с заданным объектом В качестве корня. Интерфейс IRemotingFormatter имеет те же открытые методы: Serialize и Deserialize, позволяющие выполнять глубокую сериализацию, но в режиме удаленного вызова. Поскольку сигнатуры одноименных методов интерфейсов отличаются, то конфликта имен при наследовании не происходит — В классе BinaryFormatter методы Serialize и Deserialize перегружены. Для удаленного вызова задается дополнительный параметр, что и позволяет различать, локально или удаленно выполняются процессы обмена данными.
В пространстве имен библиотеки FCL:
System.Runtime.Serialization.Formatters.Soap
находится класс SoapFormatter. Он является наследником тех же интерфейсов IFormatter и IRemotingFormatter и реализует их методы Serialize и Deserialize, позволяющие выполнять глубокую сериализацию и десериализацию при сохранении данных в формате xml. Помимо методов класса SoapFormatter, xml-сериализацию можно выполнять средствами другого класса — XmlSerializer.
Из новых средств, еще не рассматривавшихся в наших лекциях, для организации сериализации понадобятся файлы. Пространство имен ю библиотеки FCL предоставляет классы, поддерживающие ввод-вывод данных. В частности, в этом пространстве есть абстрактный класс Stream для работы с потоками данных. С одним из его потомков — классом FileStream — мы и будем работать в нашем примере.
В качестве примера промоделируем сказку Пушкина "О рыбаке и рыбке". Как вы помните, жадная старуха богатела, богатела, но после очередного желания оказалась у разбитого корыта, вернувшись в начальное состояние. Сериализация позволит нам запомнить начальное состояние, меняющееся по мере выполнения рыбкой первых пожеланий рыбака и его старухи. Десериализация вернет все в начальное состояние. Опишу класс, задающий героев пушкинской сказки:
[Serializable]
public class Personage
{
public Personage(string name, int age)
{
this.name = name; this.age = age;
}
//поля класса static int wishes;
public string name, status, wealth; int age;
public Personage couple;
//методы класса
}
Герои сказки — объекты этого класса обладают свойствами, задающими имя, возраст, статус, имущество и супруга. Имя и возраст задаются в конструкторе класса, а остальные свойства задаются в следующем методе:
public void marry (Personage couple)
{
this.couple = couple;
couple.couple = this;