События XML

60

API-интерфейс LINQ to XML предоставляет возможность регистрировать обработчик события, чтобы можно было получать уведомления всякий раз, когда объект, унаследованный от XObject, изменяется либо уже изменен.

Первое, что необходимо знать при регистрации обработчика события для объекта — событие будет инициировано на объекте, когда этот объект либо любой его производный объект изменится. Это значит, что если регистрируется обработчик события на документе или корневом элементе, то всякое изменение в дереве приведет к вызову зарегистрированного метода.

Поэтому не выдвигайте никаких предположений относительно типа данных объекта, который вызвал событие. Когда вызывается зарегистрированный метод, инициировавший событие объект будет передан в качестве отправителя события, и типом его данных будет object. Будьте очень осторожны, выполняя его приведение или обращаясь к его свойствам, либо вызывая его методы. Может оказаться, что его тип не тот, что ожидался. Эта проблема будет демонстрироваться в примерах ниже, где object на самом деле оказывается объектом XText, тогда как ожидалось, что он относится к типу XElement.

И, наконец, имейте в виду, что конструирование XML не инициирует никаких событий. Да и как оно могло бы это делать? До конструирования никаких обработчиков событий еще не зарегистрировано. Только модификация или удаление уже существующей XML-разметки может инициировать возникновение события, и только в случае, если зарегистрирован обработчик события.

XObject.Changing

Это событие возникает, когда объект, унаследованный от XObject, собирается измениться, но перед действительным его изменением. Обработчик события регистрируется добавлением объекта типа EventHandler к событию Changing объекта, как показано ниже:

myobject.Changing += new 
         EventHandler<XObjectChangeEventArgs>(MyHandler);

Делегат метода должен соответствовать следующей сигнатуре:

void MyHandler(object sender, XObjectChangeEventArgs cea)

sender — это объект, который собирается изменяться и который инициировал событие. Аргументы события изменения, сеа, содержат свойство по имени ObjectChange типа XObjectChange, указывающее тип изменения, которое должно произойти: XObjectChange.Add, XObjectChange.Name, XObjectChange.Remove или XObjectChange.Value.

XObject.Changed

Это событие инициируется после того, как объект-наследник XObject был изменен. Обработчик события регистрируется добавлением объекта типа EventHandler к событию Changed объекта, как показано ниже:

myobject.Changed += new 
       EventHandler<XObjectChangeEventArgs>(MyHandler);

Делегат метода должен соответствовать следующей сигнатуре:

void MyHandler(object sender, XObjectChangeEventArgs cea)

sender — это изменившийся объект, который вызвал возникновение события. Аргументы события change по имени, сеа, содержат свойство по имени ObjectChange типа XObjectChange, указывающее на тип произошедшего изменения: XObjectChange.Add, XObjectChange.Name, XObjectChange.Remove или XObjectChange.Value.

Несколько примеров событий

Чтобы увидеть все, что необходимо для обработки событий XObject, понадобится пример. Однако прежде чем будет показан необходимый код, понадобятся некоторые обработчики событий, приведенные ниже.

Этот метод будет зарегистрирован для обработки события изменения элемента:

public static void MyChangingEventHandler(object sender, XObjectChangeEventArgs cea)
{
      Console.WriteLine("Тип изменяемого объекта: {0}, Тип изменения: {1}",
        sender.GetType().Name, cea.ObjectChange);
}

Приведенный выше метод будет зарегистрирован в качестве обработчика события, которое происходит, когда элемент изменен.

Ранее упоминалось, что событие инициируется, когда изменяется любой из объектов-потомков зарегистрированного объекта. Чтобы лучше продемонстрировать это, предусмотрен также дополнительный метод, который зарегистрируется для вызова, когда объект изменен. Его единственное назначение — сделать более наглядным возникновение события Changed, независимо от того, изменен сам объект или его потомок, находящийся несколькими уровнями ниже. Этот метод представлен ниже.

Этот метод будет зарегистрирован для обработки события изменения XML-документа:

public static void DocumentChangedHandler(object sender, XObjectChangeEventArgs cea)
{
        Console.WriteLine("Doc: Тип изменяемого объекта: {0}, Тип изменения: {1}{2}",
        sender.GetType().Name, cea.ObjectChange, System.Environment.NewLine);
}

Единственное существенное изменение между методом DocumentChangedHandler и методом MyChangedEventHandler заключается в том, что метод DocumentChangedHandler начинает экранный вывод с префикса "Doc:", чтобы указать на то, что вызвав метод-обработчик для события Changed документа, а не обработчик того же события элемента.

Теперь взглянем на пример кода, приведенный ниже:

XElement firstEmployee;

      XDocument xDocument = new XDocument(
       new XElement("Employees", firstEmployee =
         new XElement("Employee",
           new XAttribute("type", "Programmer"),
           new XElement("FirstName", "Alex"),
           new XElement("LastName", "Erohin")),
         new XElement("Employee",
           new XAttribute("type", "Editor"),
           new XElement("FirstName", "Elena"),
           new XElement("LastName", "Volkova"))));

      Console.WriteLine(xDocument + "\n");

Пока ничего нового. Как это делалось много раз, здесь с помощью функционального конструирования создается документ XML, который затем отображается. Обратите внимание, что, как и в большинстве предыдущих примеров, сохраняется ссылка на первый элемент Employee. Это тот элемент, для событий которого регистрируются обработчики:

...
firstEmployee.Changing +=
        new EventHandler<XObjectChangeEventArgs>(MyChangingEventHandler);
firstEmployee.Changed += 
        new EventHandler<XObjectChangeEventArgs>(MyChangingEventHandler);
xDocument.Changed += 
        new EventHandler<XObjectChangeEventArgs>(DocumentChangedHandler);

В результате зарегистрировано, что первый элемент Employee должен принимать события Changing и Changed. Вдобавок зарегистрирован обработчик для получения документом события Changed. Это сделано для того, чтобы продемонстрировать, что событие получается, даже когда изменяется объект-потомок, а не сам объект, для которого зарегистрирован обработчик.

Теперь внесем изменение:

...
firstEmployee.Element("FirstName").Value = "Alexandr";

Console.WriteLine("{0}{1}", xDocument, System.Environment.NewLine);

Все, что сделано — модифицировано значение подэлемента FirstName элемента Employee. Затем отображается результирующий XML-документ. Посмотрим на результат:

Обработка события XObject

Сравнив документ в начале и конце вывода результатов, легко заметить, что значение элемента FirstName изменилось, чего и следовало ожидать.

Больше всего здесь интересует вывод, вызванный возникновением события, который находится между двумя выводами документа XML. Обратите внимание, что типом изменяемого объекта является XText. Ожидалось ли этого? Нет. Ожидался тип XElement. При установке значения элемента в виде строкового литерала легко забыть, что при этом автоматически создается объект типа XText.

Если посмотреть на вывод события, становится немного яснее, что именно происходит, когда изменяется значение элемента. Значение XText, подлежащее изменению, сначала должно быть удалено, и оно удаляется. Затем инициируется событие Changed документа. Отсюда видно, что поток событий распространяется вверх.

Затем возникает та же последовательность событий, но на этот раз объект XText добавляется. Таким образом, теперь стало ясно, что при изменении строкового значения элемента объект XText удаляется, а затем добавляется заново.

В предыдущем примере использовались именованные методы, но это вовсе не обязательно. С тем же успехом можно было бы применять анонимные методы или даже лямбда-выражения. В коде ниже показан тот же пример, но на этот раз вместо регистрации уже реализованных методов обработчиков используются лямбда-выражения, чтобы "на лету" определить код, вызываемый событиями:

XElement firstEmployee;

            XDocument xDocument = new XDocument(
             new XElement("Employees", firstEmployee =
               new XElement("Employee",
                 new XAttribute("type", "Programmer"),
                 new XElement("FirstName", "Alex"),
                 new XElement("LastName", "Erohin")),
               new XElement("Employee",
                 new XAttribute("type", "Editor"),
                 new XElement("FirstName", "Elena"),
                 new XElement("LastName", "Volkova"))));

            Console.WriteLine(xDocument + "\n");

            firstEmployee.Changing += new EventHandler<XObjectChangeEventArgs>(
                  (sender, cea) =>
                  {
                      Console.ForegroundColor = ConsoleColor.White;
                      Console.WriteLine("Тип изменяемого объекта: {0}, Тип изменения: {1}",
                          sender.GetType().Name, cea.ObjectChange);
                  });

            firstEmployee.Changed += new EventHandler<XObjectChangeEventArgs>(
                  (sender, cea) =>
                      {
                          Console.ForegroundColor = ConsoleColor.Green;
                          Console.WriteLine("Тип изменяемого объекта: {0}, Тип изменения: {1}",
                              sender.GetType().Name, cea.ObjectChange);
                      });

            xDocument.Changed += new EventHandler<XObjectChangeEventArgs>(
                  (sender, cea) =>
                      {
                          Console.ForegroundColor = ConsoleColor.DarkYellow;
                          Console.WriteLine("Doc: Тип изменяемого объекта: {0}, Тип изменения: {1}\n",
                              sender.GetType().Name, cea.ObjectChange);
                      });

            // Вносим изменение
            firstEmployee.Element("FirstName").Value = "Alexandr";

            Console.ForegroundColor = ConsoleColor.Gray;
            Console.WriteLine(xDocument);

Теперь код совершенно самодостаточен и не зависит от ранее написанных методов-обработчиков. Также были добавлены изменения цвета текста в консоли в каждом обработчике события, за счет использования свойства Console.ForegroundColor. Взглянем на результаты:

Обработка события XObject с использованием лямбда-выражений
Пройди тесты
Лучший чат для C# программистов