Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2375 / 262 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 10:

Декларативность. Атрибуты и теги

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >

Встроенные атрибуты

В библиотеке FCL определено большое число атрибутных классов, расположенных в разных пространствах имен. Например, атрибутный класс FlagsAttribute находится в пространстве System, а класс DescriptionAttribute, используемый в наших примерах, находится в пространстве имен System.ComponentModel. Атрибуты активно применяются при построении самых разных встроенных классов FCL. Уже отмечалось, сколь много атрибутов сопровождает каждую сборку.

Встроенные атрибуты активно используют и программисты, создавая собственные классы, предназначенные для решения задач проблемной области. Встроенные атрибуты, примененные к собственным классам, представляют декларативный способ, позволяющий изменить семантику выполнения методов класса. Наш пример использования атрибута Flags для перечислений демонстрировал такую возможность.

Программист, создавая класс, связывает с ним встроенный атрибут. Компилятор извлекает метаинформацию, заданную атрибутом. Поскольку встроенные атрибуты компилятору известны, компилятор принимает решение о том, какой код построить, в зависимости от того, задан атрибут или нет, какие параметры у атрибута, если они у него есть. Рассмотрим в качестве примера несколько встроенных атрибутов, часто используемых программистами при создании собственных классов.

Атрибут Serializable

Если класс объявить с атрибутом [Serializable], то в него встраивается стандартный механизм сериализации. Необходимость в сериализации объектов зачастую возникает при работе с программной системой. Под сериализацией понимают процесс сохранения объектов в долговременной памяти (файлах). Под десериализацией понимают обратный процесс - восстановление состояния объектов, хранимого в долговременной памяти. Механизмы сериализации C# и Framework .Net поддерживают два формата сохранения данных - в бинарном файле и XML- файле. В первом случае данные при сериализации преобразуются в бинарный поток символов, который автоматически при десериализации преобразуется в нужное состояние объектов. Другой возможный преобразователь (SOAP formatter) запоминает состояние объекта в формате xml. В xml-потоке сохраняются только открытые public-поля объекта. В бинарном потоке сохраняются все поля объекта, как открытые, так и закрытые. Процессом этим можно управлять, помечая некоторые поля класса атрибутом [NonSerialized], - эти поля сохраняться не будут.

Сериализация позволяет запомнить состояния системы объектов с возможностью последующего возвращения к этим состояниям. Сериализация необходима, когда завершение сеанса работы не означает завершение вычислений. В этом случае очередной сеанс работы начинается с восстановления состояния, сохраненного в конце предыдущего сеанса работы. Альтернативой сериализации является работа с обычной файловой системой, с базами данных и другими хранилищами данных. Поскольку механизмы сериализации, предоставляемые языком C#, эффективно поддерживаются .Net Framework, то при необходимости сохранения данных значительно проще и эффективнее пользоваться сериализацией, чем самому организовывать их хранение и восстановление.

Еще одно важное применение сериализации - это обмен данными удаленных систем. При удаленном обмене данными предпочтительнее формат xml из-за открытого стандарта передачи данных в Интернете по soap-протоколу, из-за открытого стандарта на структуру xml-документов. Обмен данными становится достаточно простым даже для систем, построенным на разных платформах и в разных средах разработки.

Так же как и клонирование, сериализация может быть поверхностной, когда сериализуется единственный объект, и глубокой, когда, начиная с корневого объекта, сериализуется совокупность объектов, связанных взаимными ссылками (граф объектов). Глубокую сериализацию самому организовать непросто, так как она требует, как правило, рекурсивного обхода структуры объектов.

Стандартный механизм сериализации поддерживает, что крайне приятно, глубокую сериализацию. Если по каким-либо причинам стандартная сериализация нас не устраивает, то класс следует объявить наследником интерфейса ISerialzable, реализация методов которого позволит управлять процессом сериализации.

Атрибутный класс SerializableAttribute определен следующим образом:

[ComVisibleAttribute(true)]
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Struct|AttributeTargets.Enum|AttributeTargets.Delegate, 
  Inherited = false)]
public sealed class SerializableAttribute : Attribute

Класс не может быть наследован. Атрибут сериализации может быть задан для таких сущностей, как класс, структура, перечисление и делегат.

В качестве примера рассмотрим два класса Teacher и Student, связанных взаимными ссылками и допускающих сериализацию объектов. Построим простую модель, в которой создаются объекты этих классов. В некоторый момент состояние этих объектов запоминается за счет использования механизма сериализации. Затем состояние объектов изменяется. А потом, с использованием десериализации, происходит возврат к сохраненному состоянию. Начнем с объявления класса Teacher:

[Serializable]
    class Teacher
    {
        string name;
        Student[] students;
        public Teacher(string name, Student[] students)
        {
            this.name = name;
            this.students = students;
        }
        public Student[] Students
        {
            get { return students; }
        }
    }

Для класса задан атрибут сериализации. У класса два поля: одно задает имя учителя, другое - его учеников. У класса есть конструктор с параметрами и свойство, позволяющее получить доступ к закрытому полю students.

Класс Student также устроен достаточно просто:

[Serializable]
    class Student
    {
        string name;
        Teacher teacher;
        string topic;
        public Student(string name, Teacher teacher)
        {
            this.name = name;
            this.teacher = teacher;
        }
        public string Topic
        {
            get { return topic; }
            set { topic = value; }
        }
    }

Для класса также задан атрибут сериализации. У класса три поля, которые задают имя студента, учителя и тему работы, выполняемой под руководством учителя. У класса есть конструктор с параметрами и свойство, позволяющее получить доступ к закрытому полю topic.

А теперь построим модель, в которой создаются и существуют объекты этих классов. Как обычно, в классе Testing построен соответствующий метод:

public void TestTeacherAndStudents()
    {
        string teacherName = "Иванов Л.Д.";
        Teacher teacher;
        Student[] students = new Student[3];
        string[] studNames = 
        {"Зайцев Г.Ю.","Гулевич С.А.","Маргулис В.А."};
        teacher = new Teacher(teacherName, students);
        students[0] = new Student(studNames[0], teacher);
        students[1] = new Student(studNames[1], teacher);
        students[2] = new Student(studNames[2], teacher);

В нашей модели созданы объекты teacher и students, задающие учителя и его студентов. Зададим теперь темы работ студентов и сохраним это состояние объектов в бинарном файле.

students[0].Topic = "thema0";
students[1].Topic = "thema1";
students[2].Topic = "thema2";
Console.WriteLine("Темы студентов");
for (int i = 0; i < 3; i++)
    Console.WriteLine("Студент {0}, тема: {1} ",
        studNames[i], students[i].Topic);
       
Stream tasFile, satFile;
tasFile = File.Create("teach1.bin");
satFile = File.Create("teach2.bin");
BinaryFormatter bif = new BinaryFormatter();
bif.Serialize(tasFile, teacher);
bif.Serialize(satFile, students[0]);
tasFile.Close();
satFile.Close();
Console.WriteLine("Темы запомнили!");

Для полноты описания картины сериализации объекты сохраняются в двух разных файлах. В обоих случаях сохраняются все четыре объекта. Граф объектов один и тот же, но корневой объект разный. В одном случае сохранение начинается с учителя, в другом - с одного из студентов. Поскольку при сериализации используется бинарный форматер, то сохраняются все поля всех объектов. Заметьте, все классы, участвующие в сериализации, должны иметь атрибут Serializable. Метод Serialize определен для класса только тогда, когда у класса есть соответствующий атрибут.

Представим себе, что после некоторого обсуждения темы работ решено было изменить:

Console.WriteLine("Меняем темы!");
  students[0].Topic = "thema7";
  students[1].Topic = "thema8";
  students[2].Topic = "thema9";
  teacher = null;
  for (int i = 0; i < 3; i++)
      Console.WriteLine("Студент {0}, тема: {1} ",
          studNames[i], students[i].Topic);

Для полноты картины у студентов не стало учителя. Видимо, произошел конфликт. Но потом все уладилось, и нужно вернуться к исходному состоянию. В этом поможет десериализация.

Console.WriteLine("Десериализация. Восстанавливаем состояние");
  tasFile = File.Open("teach1.bin",FileMode.Open, FileAccess.Read);      
  bif = new BinaryFormatter();
  teacher = (Teacher)bif.Deserialize(tasFile);
  students = teacher.Students;
  for (int i = 0; i < 3; i++)
      Console.WriteLine("Студент {0}, тема: {1} ",
          studNames[i], students[i].Topic);
    }

Используя корневой объект, в данном случае teacher, восстанавливаем весь граф объектов, связанных с корнем. Но, заметьте, остальные объекта графа - это внутренняя структура, связанная с корнем. Восстановить объект students нужно явно, используя свойство Students объекта teacher. На рис. 9.5 показаны результаты работы тестового примера.

Сериализация объектов

Рис. 9.5. Сериализация объектов
< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Михаил Алексеев
Михаил Алексеев
Россия, Уфа, УГАТУ, 2002
Олег Корсак
Олег Корсак
Латвия, Рига