Компоненты

Компоненты — это главное богатство системы .NET. Роль компонент в .NET настолько велика, что языки программирования этой системы, например, C#, часто называют не просто объектно-ориентированными, а компонентно-ориентированными.

Все компоненты условно можно подразделить на две группы: стандартные компоненты среды .NET Framework, созданные в фирме Microsoft, и компоненты сторонних разработчиков. Компоненты — это строительные блоки, из которых мы можем быстро создавать новые программы. Компоненты охватывают почти все области программирования. А если для решения какого-то класса задач нет готового компонента, то его можно создать. Поэтому так важно научиться разрабатывать свои собственные компоненты.

Определение компонента

Что представляет собой компонент?

Компонент — это отдельный независимый модуль, предназначенный для многократного повторного использования и предоставляемый пользователю-программисту в откомпилированном виде.

Рассмотрим подробнее ключевые характеристики компонента.

Мы говорим, что компонент — это независимый модуль. Это означает, что каждый компонент самодостаточен, он имеет всё, что требуется ему для полноценного функционирования.

Компонент предназначен для многократного использования, т.е. в любой программе, создаваемой для работы в среде .NET Framework, можно подключить созданный ранее компонент и воспользоваться всеми его функциями. Программа, использующая компонент, называется клиентом. Одним компонентом одновременно может воспользоваться любое количество клиентов.

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

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

Благодаря особенностям работы средств языка C#, любой его класс полностью соответствует общему определению компонента. Например, если откомпилировать класс, то его можно использовать в разных программах. Но является ли этот класс компонентом? Нет, не является. Для того, чтобы класс стал компонентом, необходимо, чтобы класс соответствовал компонентной модели. Сделать это несложно. Класс должен реализовать интерфейс System.ComponentModel.IComponent. Хотя реализация интерфейса IComponent несложна, проще использовать класс System.ComponentModel.Component. Этот класс обеспечивает стандартную реализацию интерфейса IComponent, что сокращает время и объем работы программиста.

Рассмотрим подробнее интерфейс IComponent и класс Component.

Интерфейс IComponent

Интерфейс IComponent имеет только одно свойство Site и одно событие Disposed.

Объявление свойства Site:

ISite Site {get; set;}

Данное свойство получает или устанавливает узел компонента, который идентифицирует компонент в контейнере. Если компонент не хранится в контейнере, то свойство имеет значение null.

Объявление события Disposed:

event EventHandler Disposed

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

Интерфейс IComponent также наследует интерфейс System.IDisposable, в котором определен метод Dispose():

void Dispose()

Это метод освобождает ресурсы, используемые объектом.

Класс Component

Именно на базе класса Component программисты обычно и создают свои собственные компоненты.

Свойства класса

В классе Component определено два открытых свойства: Container и Site.

1)Объявление свойства Container выглядит так:

public IContainer Container {get;}

Свойство Container возвращает контейнер, который содержит вызывающий компонент. Если компонента нет в контейнере, то свойство возвращает значение null.

Замечание. Свойство Container устанавливается не самим компонентом, а контейнером.

2)Объявление свойства Site:

public virtual ISite Site {set; get;}

Свойство Site возвращает или устанавливает объект типа ISite, связанный с компонентом. Оно возвращает значение null, если компонента нет в контейнере.

И это свойство устанавливается контейнером, а не самим компонентом.

Методы класса

Класс Component содержит два метода. Первый — переопределенный метод ToString(), второй — метод Dispose(), имеющий две формы:

а) первая форма имеет вид:

public void Dispose()

Этот вариант освобождает любые ресурсы, используемые компонентом. Этот метод реализует метод Dispose(), определённый в интерфейсе IDisposable. Для освобождения компонента и занимаемых им ресурсов клиент вызывает именно этот вариант метода Dispose().

б) вторая форма метода:

protected virtual void Dispose(bool pr)

Если pr равен true, то метод освобождает как управляемые, так и неуправляемые ресурсы. Если pr равен false, то метод освобождает только неуправляемые ресурсы. Так как эта версия защищена (protected), то её нельзя вызвать из кода клиента. В клиенте вызывается первая версия, а она в свою очередь вызывает вторую версию.

Пример реализации компонента

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

Шифр Цезаря. Дан текст, состоящий из заглавных латинских букв. Зашифровать его по правилу: A заменить на C, B — на D и так далее, последние две буквы в алфавите заменить на начальные: Y — на A, Z — на B. Составить процедуру и для расшифровки текста.

Разработаем компонент, который будет содержать два метода: Shifr() — шифрование текста и Deshifr() — расшифровка текста. Создадим консольное приложение с именем проекта ShifrLib и наберем следующий текст:

// Компонент для шифрования и расшифрования текста

// по методу Цезаря

// -----------------------------------------------

using System;

using System.ComponentModel;

namespace ShifrLib

{

   public class ShifrComp: Component

   {

      // Зашифровать строку

      // ------------------

      public string Shifr(string s0)

      {

         string s = "";

         for(int i = 0; i < s0.Length; i++)

         {

            if(s0[i] >= 'A' && s0[i] <= 'X')

               s += (char) (s0[i] + 2);

            else if (s0[i] == 'Y')

               s += 'A';

            else if (s0[i] == 'Z')

               s += 'B';

            else

               s += s0[i];

         }

         return s;

      }

      

      // Расшифровать строку

      // -------------------

      public string Deshifr(string s0)

      {

         string s = "";

         for(int i = 0; i < s0.Length; i++)

         {

            if(s0[i] >= 'C' && s0[i] <= 'Z')

               s += (char) (s0[i] - 2);

            else if (s0[i] == 'A')

               s += 'Y';

            else if (s0[i] == 'B')

               s += 'Z';

            else

               s += s0[i];

         }

         return s;

      }

   }

}

В настройках проекта Project/Project Options... во вкладке Application (Приложение) для Output type (Тип выходного объекта) выбираем Class Library, т.е. создаем не exe-файл, а dll-библиотеку. Теперь компилируем. Если нет ошибок, то в папке Bin/Debug появится файл ShifrLib.dll. В этом файле находится наш компонент ShifrComp, готовый к дальнейшему использованию.

Тестирование компонента

Теперь необходимо проверить работу созданного компонента. Используем проект консольного приложения. Прежде чем запускать программу на выполнение, необходимо подключить библиотеку ShifrLib.dll к нашему проекту:

Вот возможный вариант тестовой программы:

// Программа-клиент для тестирования компонента ShifrComp

// ------------------------------------------------------

using System;

using ShifrLib; // Пространство имен компонента ShifrComp

namespace Test_ShifrLib

{

   class Program

   {

      public static void Main(string[] args)

      {

         Console.WriteLine("Тестирование компонента");

         ShifrComp x = new ShifrComp();

         string s = "RUSSIAN LANGUAGE";

         Console.WriteLine("Исходная строка:\n"+s);

         string s1 = x.Shifr(s);

         Console.WriteLine("Зашифрованная строка:\n"+s1);

         string s2 = x.Deshifr(s1);

         Console.WriteLine("После расшифровки:\n"+s2);

         x.Dispose(); // Освобождение ресурсов

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

--Тестирование компонента--

Исходная строка:

RUSSIAN LANGUAGE

Зашифрованная строка:

TWUUKCP NCPIWCIG

После расшифровки:

RUSSIAN LANGUAGE

Пояснения к программе. В тексте программы подключено пространство имен компонента ShifrLib. С помощью метода Dispose() программа-клиент освобождает ресурсы, которые использовал компонент. В данном примере можно было не использовать этот метод, так как механизм сбора мусора освобождает ресурсы компонента без нашего участия. Но вот когда это произойдет — заранее не известно. Поэтому, если ресурсы компонента необходимы другим «участникам» вычислительного процесса, то желательно такие ресурсы освобождать немедленно. А вообще-то, метод Dispose() есть смысл вызывать всегда, так как он является частью контракта компонентной модели.

Переопределение метода Dispose()

В первой версии программы компонент ShifrComp не использовал системных ресурсов, поэтому не было необходимости переопределять метод Dispose(bool). Если компонент будет использовать системные ресурсы, например, работать с файлами, то метод Dispose(bool) необходимо обязательно переопределить. Дело в том, что вызов стандартной реализации этого метода приводит только к удалению ссылок на компонент, но если компонент открывал файл, то этот файл так и останется открытым до тех пор, пока сборщик мусора не проведет очередную «чистку». Выход один: компонент сам должен реализовать все необходимые действия в переопределенном методе Dispose(bool).

Переопределяя метод Dispose(bool), необходимо придерживаться следующих правил:

  1. При вызове метода Dispose(bool) с аргументом true переопределяемая версия метода должна освобождать все связанные с компонентом ресурсы: управляемые и неуправляемые. Если метод вызывается с аргументом false, то освобождаться должны только неуправляемые ресурсы, если они имеются.

  2. Метод Dispose(bool) должен вызываться многократно, не вызывая при этом ни каких проблем.

  3. Метод Dispose(bool) должен вызывать метод Dispose(bool), реализованный в базовом классе.

  4. Деструктор, созданный для компонента, должен вызывать метод Dispose(false).

Чтобы выполнить правило 2, компонент должен отслеживать момент своего освобождения. Для этого можно использовать закрытое поле — индикатор состояния isDisposed.

Компонент, реализующий метод Dispose(bool), схематично может выглядеть так:

public class ShifrComp: Component

{

   bool isDisposed; // true, если компонент освобождается


   public ShifrComp()

   {

      isDisposed = false; // Компонент не освобождается

      // ...


   }


   ~ShifrComp()

   {

      Dispose(false);

   }


   protected override void Dispose(bool dispAll)

   {

      if(!isDisposed)

      {

         if(dispAll)

         {

            // Освобождаем управляемые ресурсы

            isDisposed = true;

         }

         // Неуправляемые ресурсы не нужно освобождать

         base.Dispose(dispAll);

      }

   }

}

Компонент с переопределенным методом Dispose(bool)

Для демонстрации использования метода Dispose(bool) в нашем компоненте добавим запись в файл результаты всех вызовов по шифрованию и дешифровки. Метод Dispose(bool) должен будет закрывать файл, когда компонент больше не нужен. Вот измененный текст компонента:

// Измененный вариант компонента для шифрования

// и расшифрования текста по методу Цезаря.

// Добавлены: запись в файл всех операций,

// конструктор, деструктор, переопределен метод

// Dispose()

// --------------------------------------------

using System;

using System.ComponentModel;

using System.IO;

namespace ShifrLib

{

   public class ShifrComp: Component

   {

      static int useID = 0;

      int id;          // Идентификационный номер экземпляра

      bool isDisposed; // true, если компонент освобождается

      FileStream f;


      // Конструктор

      public ShifrComp()

      {

         isDisposed = false; // Компонент не освобождается

         try{

            f = new FileStream("Shifr" + useID,

                               FileMode.Create);

            id = useID;

            useID++;

         }

         catch(FileNotFoundException exc)

         {

            Console.WriteLine(exc);

            f = null;

         }

      }

      

      // Деструктор

      ~ShifrComp()

      {

         Console.WriteLine("Деструктор для компонента"

                           + id);

         Dispose(false);

      }


      // Зашифровать строку

      // ------------------

      public string Shifr(string s0)

      {

         // Запрет на использование уже

         // освобожденного компонента

         if(isDisposed)

         {

            Console.WriteLine("Ошибка:" + " компонент уже разрушен");

            return null;

      }

         string s = "";

         for(int i = 0; i < s0.Length; i++)

         {

            if(s0[i] >= 'A' && s0[i] <= 'X')

               s += (char) (s0[i] + 2);

            else if (s0[i] == 'Y')

               s += 'A';

            else if (s0[i] == 'Z')

               s += 'B';

            else

               s += s0[i];

         }


         // Сохраняем результат шифрования в файле

         for(int i = 0; i <s.Length; i++)

            f.WriteByte((byte) s[i]);

         return s;

      }


      // Расшифровать строку

      // -------------------

      public string Deshifr(string s0)

      {

         // Запрет на использование уже

         // освобожденного компонента

         if(isDisposed)

         {

            Console.WriteLine("Ошибка:" +

                              " компонент уже разрушен");

            return null;

         }

         string s = "";

         for(int i = 0; i < s0.Length; i++)

         {

            if(s0[i] >= 'C' && s0[i] <= 'Z')

               s += (char) (s0[i] - 2);

            else if (s0[i] == 'A')

               s += 'Y';

            else if (s0[i] == 'B')

               s += 'Z';

            else

               s += s0[i];

         }


         // Сохраняем результат дешифрования в файле

         for(int i = 0; i <s.Length; i++)

            f.WriteByte((byte) s[i]);

         return s;

      }


      protected override void Dispose(bool dispAll)

      {

      Console.WriteLine("Dispose(" + dispAll +

                        ") для компонента " + id);

         if(!isDisposed)

         {

            if(dispAll) //Освобождаем управляемые ресурсы

            {

               Console.WriteLine("Закрытие файла для " +

                                 "компонента " + id);

               f.Close(); // Закрытие файла

               isDisposed = true;

            }

            // Неуправляемые ресурсы не нужно освобождать

            base.Dispose(dispAll);

         }

      }

   }

}

Текст тестирующей программы:

// Программа-клиент для тестирования компонента ShifrComp

// ------------------------------------------------------

using System;

using System.IO;

using ShifrLib; // Пространство имен компонента ShifrComp

namespace Test_ShifrLib

{

   class Program

   {

      public static void Main(string[] args)

      {

         Console.WriteLine("--Тестирование компонента--");

         ShifrComp x = new ShifrComp();

         string s = "RUSSIAN LANGUAGE";

         Console.WriteLine("Исходная строка:\n"+s);

         string s1 = x.Shifr(s);

         Console.WriteLine("Зашифрованная строка:\n"+s1);

         string s2 = x.Deshifr(s1);

         Console.WriteLine("После расшифровки:\n"+s2);

         s = "HELLO, MY FRIEND!";

         Console.WriteLine("Исходная строка:\n"+s);

         s1 = x.Shifr(s);

         Console.WriteLine("Зашифрованная строка:\n"+s1);

         s2 = x.Deshifr(s1);

         Console.WriteLine("После расшифровки:\n"+s2);

         x.Dispose(); // Освобождение ресурсов

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

--Тестирование компонента--

Исходная строка:

RUSSIAN LANGUAGE

Зашифрованная строка:

TWUUKCP NCPIWCIG

После расшифровки:

RUSSIAN LANGUAGE

Исходная строка:

HELLO, MY FRIEND!

Зашифрованная строка:

JGNNQ, OA HTKGPF!

После расшифровки:

HELLO, MY FRIEND!

Dispose(True) для компонента 0

Закрытие файла для компонента 0


Назад
На верх
Вперёд
Hosted by uCoz