Добавить в цитаты Настройки чтения

Страница 320 из 371

    this.status ="крестьянин";

    this.wealth ="рыбацкая сеть";

    this.couple.status = "крестьянка";

    this.couple.wealth = "корыто";

    SaveState ();

}

Предусловие метода предполагает, что метод вызывается один раз главным героем (рыбаком). В методе устанавливаются взаимные ссылки между героями сказки, их начальное состояние. Завершается метод сохранением состояния объектов, выполняемого при вызове метода SaveState:

void SaveState ()

{

    BinaryFormatter bf = new BinaryFormatter ();

    FileStream fs = new FileStream

         ("State.bin",FileMode.Create, FileAccess.Write);

    bf.Serialize(fs,this);

    fs.Close ();

}

Здесь и выполняется сериализация графа объектов. Как видите, все просто. Вначале создается форматер — объект bf класса BinaryFormatter. Затем определяется файл, в котором будет сохраняться состояние объектов, — объект fs класса FileStream. Заметьте, в конструкторе файла, кроме имени файла, указываются его характеристики: статус, режим доступа. На деталях введения файлов я останавливаться не буду. Теперь, когда основные объекты определены, остается вызвать метод Serialize объекта bf, которому в качестве аргументов передается объект fs и текущий объект, представляющий корневой объект графа объектов, которые подлежат сериализации. Глубокая сериализация, реализуемая в данном случае, не потребовала от нас никаких усилий.

Нам понадобится еще метод, описывающий жизнь героев сказки:

public Personage AskGoldFish()

{

    Personage fisher = this;

    if (fisher.name == "рыбак")

    {

         wishes++;

         switch (wishes)

         {

             case 1: ChangeStateOne();break;

             case 2: ChangeStateTwo();break;

             case 3: ChangeStateThree();break;

             default: BackState(ref fisher);break;

         }

    }

    return(fisher);

}//AskGoldFish

Метод реализует анализ желаний героини сказки. Первые три желания исполняются, и состояние героев меняется:

void ChangeStateOne ()

{

    this.status = "муж дворянки";

    this.couple.status = "дворянка";

    this.couple.wealth = "имение";

}

void ChangeStateTwo ()

{

    this.status = "муж боярыни";

    this.couple.status = "боярыня";

    this.couple.wealth = "много поместий";

}

void ChangeStateThree()

{

    this.status = "муж государыни";

    this.couple.status = "государыня";

    this.couple.wealth = "страна";

}

Начиная с четвертого желания, все возвращается в начальное состояние — выполняется десериализация графа объектов:

void BackState(ref Personage fisher)

{

    BinaryFormatter bf = new BinaryFormatter ();

    FileStream fs = new FileStream

        ("State.bin",FileMode.Open, FileAccess.Read);





    fisher = (Personage)bf.Deserialize(fs);

    fs.Close();

}

Обратите внимание, что у метода есть аргумент, передаваемый по ссылке. Этот аргумент получает значение — ссылается на объект, создаваемый методом Deserialize. Без аргумента метода не обойтись, поскольку возвращаемый методом объект нельзя присвоить текущему объекту this. Важно также отметить, что метод Deserialize восстанавливает весь граф объектов, возвращая в качестве результата корень графа. В классе определен еще один метод, сообщающий о текущем состоянии объектов:

public void About()

{

    Console.WriteLine("имя = {0}, возраст = {1},"+

    "статус = {2}, состояние ={3}",name,age,status, wealth);

    Console.WriteLine("имя = {0}, возраст = {1}," +

    "статус = {2}, состояние ={3}", this.couple.name,

    this.couple.age,this.couple.status, this.couple.wealth);

}

Для завершения сказки нам нужно в клиентском классе создать ее героев:

public void TestGoldFish()

{

    Personage fisher = new Personage("рыбак", 70);

    Personage wife = new Personage("старуха", 70);

    fisher.marry(wife);

    Console.WriteLine("До золотой рыбки"); fisher.About();

    fisher = fisher.AskGoldFish();

    Console.WriteLine("Первое желание"); fisher.About();

    fisher = fisher.AskGoldFish();

    Console.WriteLine("Второе желание"); fisher.About();

    fisher = fisher.AskGoldFish();

    Console.WriteLine("Третье желание"); fisher.About();

    fisher = fisher.AskGoldFish();

    Console.WriteLine("Еще хочу"); fisher.About();

    fisher = fisher.AskGoldFish();

    Console.WriteLine("Хочу, но уже поздно"); fisher.About();

}

На рис. 19.6 показаны результаты исполнения сказки.

Рис. 19.6. Сказка о рыбаке и рыбке

Что изменится, если перейти к сохранению данных в xml-формате? немногое. Нужно лишь заменить объявление форматера:

void SaveStateXML()

{

    SoapFormatter sf = new SoapFormatter();

    FileStream fs = new FileStream

           ("State.xml",FileMode.Create, FileAccess.Write);

     sf.Serialize(fs,this);

     fs.Close ();

}

void BackStateXML(ref Personage fisher)

{

      SoapFormatter sf = new SoapFormatter();

      FileStream fs = new FileStream

           ("State.xml",FileMode.Open, FileAccess.Read);

      fisher = (Personage)sf.Deserialize(fs);

      fs.Close ();

}

Клиент, работающий с объектами класса, этих изменений и не почувствует. Результаты вычислений останутся теми же, что и в предыдущем случае. Правда, файл, сохраняющий данные, теперь выглядит совсем по-другому. Это обычный xml-документ, который мог быть создан в любом из приложений. Вот как выглядит этот документ, открытый в браузере Internet Explorer.

Рис. 19.7. XML-документ, сохраняющий состояние объектов

Интерфейс ISerializable

При необходимости можно самому управлять процессом сериализации. В этом случае наш класс должен быть наследником интерфейса ISerializable. Класс, наследующий этот интерфейс, должен реализовать единственный метод этого интерфейса GetObjectData и добавить защищенный конструктор. Схема сериализации и десериализации остается и в этом случае той же самой. Можно использовать как бинарный форматер, так и soap-форматер. Но теперь метод Serialize использует не стандартную реализацию, а вызывает метод GetObjectData , управляющий записью данных. Метод Deserialize, в свою очередь, вызывает защищенный конструктор, создающий объект и заполняющий его поля сохраненными значениями.

Конечно, возможность управлять сохранением и восстановлением данных дает большую гибкость и позволяет, в конечном счете, уменьшить размер файла, хранящего данные, что может быть крайне важно, особенно если речь идет об обмене данными с удаленным приложением. Если речь идет о поверхностной сериализации, то атрибут NonSerialized, которым можно помечать поля, не требующие сериализации, как правило, достаточен для управления эффективным сохранением данных. Так что управлять имеет смысл только глубокой сериализацией, когда сохраняется и восстанавливается граф объектов. Но, как уже говорилось, это может быть довольно сложным занятием, что будет видно и для нашего простого примера с рыбаком и рыбкой.