Пространство имен System.Collections — КиберПедия 

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

Особенности сооружения опор в сложных условиях: Сооружение ВЛ в районах с суровыми климатическими и тяжелыми геологическими условиями...

Пространство имен System.Collections

2019-09-26 441
Пространство имен System.Collections 0.00 из 5.00 0 оценок
Заказать работу

В пространстве имен System.Collections определены наборы стандартных коллекций и интерфейсов, которые реализованы в этих коллекциях. В таблице 2 приведены наиболее важные интерфейсы, часть из которых уже изучались в разделе "Стандартные интерфейсы.NET".

Таблица 2. Интерфейсы пространства имен System.Collections

Интерфейс Назначение
ICollection Определяет общие характеристики (например, размер) для набора элементов
IComparer Позволяет сравнивать два объекта
IDictionary Позволяет представлять содержимое объекта в виде пар "имя-значение"
IDictionaryEnumerator Используется для нумерации содержимого объекта, поддерживающего интерфейс IDictionary
IEnumerable Возвращает интерфейс IEnumerator для указанного объекта
IEnumerator Обычно используется для поддержки оператора foreach в отношении объектов
IHashCodeProvider Возвращает хэш-код для реализации типа с применением выбранного пользователем алгоритма хэширования
Ilist Поддерживает методы добавления, удаления и индексирования элементов в списке объектов

В таблице  3 перечислены основные коллекции, определенные в пространстве System.Collections.

Таблица 3. Коллекции пространства имен System.Collections

Класс Назначение Важнейшие из реализованных интерфейсов
ArrayList Массив, динамически изменяющий свой размер IList, ICollection, IEnumerable, ICloneable
BitArray Компактный массив для хранения битовых значений ICollection, IEnumerable, ICloneable
Hashtable Хэш-таблица IDictionary, ICollection, IEnumerable, ICloneable
Queue Очередь ICollection, ICloneable, IEnumerable
SortedList Коллекция, отсортированная по ключам. Доступ к элементам — по ключу или по индексу IDictionary, ICollection, IEnumerable, ICloneable
Stack Стек ICollection, IEnumerable

Пространство имен System.Collections.Specialized включает специализированные коллекции, например, коллекцию строк StringCollection и хэш-таблицу со строковыми ключами StringDictionary.

В качестве примера стандартной коллекции рассмотрим класс ArrayList.

Класс ArrayList

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

По умолчанию при создании объекта типа ArrayList строится массив из 16 элементов типа object. Можно задать желаемое количество элементов в массиве, передав его в конструктор или установив в качестве значения свойства Capacity, например:

ArrayList arr1 = new ArrayList();     // создается массив из 16 элементов

ArrayList arr2 = new ArrayList(1000); // создается массив из 1000 элементов

ArrayList arr3 = new ArrayList();

arr3.Capacity = 1000;                   // количество элементов задается

Класс ArrayList реализован через класс Array, то есть содержит закрытое поле этого класса. Поскольку все типы в C# являются потомками класса object, массив может содержать элементы произвольного типа. Даже если в массиве хранятся обычные целые числа, то есть элементы значимого типа, внутренний класс является массивом ссылок на экземпляры типа object, которые представляют собой упакованный тип-значение. Соответственно, при занесении в массив выполняется упаковка, а при извлечении — распаковка элемента.

Если при добавлении элемента в массив оказывается, что фактическое количество элементов массива превышает его емкость, она автоматически удваивается, то есть происходит повторное выделение памяти и переписывание туда всех существующих элементов. Пример занесения элементов в экземпляр класса ArrayList:

arr1.Add(123); arr1.Add(-2); arr1.Add("Вася");

Доступ к элементу выполняется по индексу, однако при этом необходимо явным образом привести полученную ссылку к целевому типу, например:

int a = (int) arr1[0];

int b = (int) arr1[1];

string s = (string) arr1[2];

Попытка приведения к типу, не соответствующему хранимому в элементе, вызывает генерацию исключения InvalidCastException.

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

Недостатком этого решения является то, что для каждого метода стандартной коллекции приходится описывать метод-оболочку, вызывающий стандартный метод. Хотя это и несложно, но несколько неизящно. В C#, начиная с версии 2.0, появились классы-прототипы (generics), позволяющие решить эту проблему.

Классы-прототипы (generics)

Классы-прототипы (generics) — это классы, имеющие в качестве параметров типы данных. Чаще всего их применяют для хранения данных, то есть в качестве контейнерных классов, или коллекций. Во вторую версию библиотеки.NET добавлены параметризованные коллекции для представления основных структур данных, применяющихся при создании программ — стека, очереди, списка, словаря и т. д. Эти коллекции, расположенные в пространстве имен System.Collections.Generic, дублируют аналогичные коллекции пространства имен System.Collections. В таблице 4 приводится соответствие между обычными и параметризованными коллекциями библиотеки.NET (параметры, определяющие типы данных, хранимых в коллекции, указаны в угловых скобках).

Таблица 9.4. Параметризованные коллекции библиотеки.NET версии 2.0

Класс-прототип (версия 2.0) Обычный класс
Comparer<T> Comparer
Dictionary<K,T> HashTable
LinkedList<T>
List<T> ArrayList
Queue<T> Queue
SortedDictionary<K,T> SortedList
Stack<T> Stack<T>

В качестве примера рассмотрим применение универсального "двойника" класса ArrayList — класса List<T> — для хранения коллекции объектов известных нам классов Monster и Daemon, а также для хранения целых чисел.

using System;

using System.Collections.Generic;

using System.Text;

namespace ConsoleApplication1

{

using MonsterLib; // библиотека, в которой хранятся классы Monster и Daemon

class Program

{

   static void Main()

   {

       List<Monster> stado = new List<Monster>();

       stado.Add(new Monster("Monia"));

       stado.Add(new Monster("Monk"));

       stado.Add(new Daemon ("Dimon", 3));

       foreach (Monster x in stado) x.Passport();

       List<int> lint = new List<int>();

        lint.Add(5); lint.Add(1); lint.Add(3);

       lint.Sort();

       int a = lint[2];

       Console.WriteLine(a);

       foreach (int x in lint) Console.Write(x + " ");

}}}

Листинг 7. Использование универсальной коллекции List<T>

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

Monster Monia health = 100 ammo = 100

Monster Monk health = 100 ammo = 100

Daemon Dimon health = 100 ammo = 100 brain = 3

5

1 3 5

В листинге 7 две коллекции. Первая (stado) содержит элементы пользовательских классов, которые находятся в библиотеке MonsterLib.dll. В коллекции, для которой объявлен тип элементов Monster, благодаря полиморфизму можно хранить элементы любого производного класса, но не элементы других типов. Достоинством такого ограничения является то, что компилятор может выполнить контроль типов, что повышает надежность программы и упрощает поиск ошибок.

Коллекция lint состоит из целых чисел, причем для работы с ними не требуются ни операции упаковки и распаковки, ни явные преобразования типа при получении элемента из коллекции.

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

Задания на лабораторную работу.

Контрольные вопросы.

1. Изучите разделы стандарта C#, касающиеся интерфейсов.

2. Изучите по справочной системе основные стандартные интерфейсы.NET.

3. Чем интерфейс отличается от абстрактного класса?

4. Должен ли класс реализовывать все методы всех своих интерфейсов-предков?

5. Какие операции используются для проверки, реализует ли класс заданный интерфейс?

6. Перечислите стандартные интерфейсы.NET, которые определяют возможности сортировки, клонирования и просмотра объектов с помощью оператора foreach.

7. Опишите, как используется конструкция yield.

8. Изучите по справочной системе состав пространства имен System.Collections.

9. Изучите по справочной системе свойства и методы класса ArrayList.

10. Изучите разделы стандарта C#, касающиеся классов-прототипов.

11. Изучите по справочной системе состав пространства имен System.Collections.Generic. Опишите основные виды абстрактных структур данных.

12. Допускает ли структура "линейный список" прямой доступ к своим элементам?

13. В чем основная задача хеш-функции?

14. В каких задачах используется стек? Приведите примеры.

15. Сравните время поиска элемента в линейном списке и в дереве поиска.

16. В чем преимущество массива перед другими структурами данных?

17. Опишите состав пространства имен System.Collections и дайте характеристику основных типов-коллекций.

18. Какие новые контейнеры появились в библиотеке классов для версии С# 2.0?

19. В чем преимущества контейнеров версии С# 2.0?

20. Какие методы содержит класс ArrayList?


 


Лабораторная работа 17.  
Делегаты и события

Теоретические сведения.

Делегаты

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

Описание делегатов

Описание делегата задает сигнатуру методов, которые могут быть вызваны с его помощью:

[ атрибуты ] [ спецификаторы ] delegate тип имя_делегата ([ параметры ])

Допускаются спецификаторы new, public, protected, internal и private. Тип описывает возвращаемое значение методов, вызываемых с помощью делегата, а необязательными параметрами делегата являются параметры этих методов. Делегат может хранить ссылки на несколько методов и вызывать их поочередно; естественно, что сигнатуры всех методов должны совпадать.

Пример описания делегата:

public delegate void D (int i);

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

Использование делегатов

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

Делегаты применяются в основном для следующих целей:

· получения возможности определять вызываемый метод не при компиляции, а во время выполнения программы;

· обеспечения связи между объектами по типу "источник — наблюдатель";

· создания универсальных методов, в которые можно передавать другие методы;

· поддержки механизма обратных вызовов.

Рассмотрим сначала пример реализации первой из этих целей. В листинге 1 объявляется делегат, с помощью которого один и тот же оператор используется для вызова двух разных методов (C00l и Hack).

using System;

namespace ConsoleApplication1

{

delegate void Del (ref string s);          // объявление делегата

class Class1

{

   public static void C00l (ref string s)             // метод 1

   {

       string temp = "";

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

       {

           if (s[i] == 'o' || s[i] == 'O') temp += '0';

           else if (s[i] == 'l')          temp += '1';

           else                             temp += s[i];

       }

       s = temp;

   }

   public static void Hack (ref string s)             // метод 2

   {

       string temp = "";

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

           if (i / 2 * 2 == i) temp += char.ToUpper(s[i]);

           else             temp += s[i];

       s = temp;

   }

   static void Main()

   {

       string s = "cool hackers";

       Del d;                                // экземпляр делегата

       for (int i = 0; i < 2; ++i)

       {

           d = new Del(C00l);         // инициализация методом 1

           if (i == 1) d = new Del(Hack); // инициализация методом 2

           d(ref s); // использование делегата для вызова методов

           Console.WriteLine(s);

       }

   }

}

}

Листинг 1. Использование делегата

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

c001 hackers

C001 hAcKeRs

Использование делегата имеет тот же синтаксис, что и вызов метода. Если делегат хранит ссылки на несколько методов, они вызываются последовательно в том порядке, в котором были добавлены в делегат.

Добавление метода в список выполняется либо с помощью метода Combine, унаследованного от класса System.Delegate, либо, что удобнее, с помощью перегруженной операции сложения. Вот как выглядит измененный метод Main из предыдущего листинга, в котором одним вызовом делегата выполняется преобразование исходной строки сразу двумя методами.

static void Main()

{

   string s = "cool hackers";

   Del d = new Del(C00l);

   d += new Del(Hack);        // добавление метода в делегат

   d(ref s);

   Console.WriteLine(s);      // результат: C001 hAcKeRs

}

При вызове последовательности методов с помощью делегата необходимо учитывать следующее:

· сигнатура методов должна в точности соответствовать делегату;

· методы могут быть как статическими, так и обычными методами класса;

· каждому методу в списке передается один и тот же набор параметров;

· если параметр передается по ссылке, изменения параметра в одном методе отразятся на его значении при вызове следующего метода;

· если параметр передается с ключевым словом out или метод возвращает значение, результатом выполнения делегата является значение, сформированное последним из методов списка (в связи с этим рекомендуется формировать списки только из делегатов, имеющих возвращаемое значение типа void);

· если в процессе работы метода возникло исключение, не обработанное в том же методе, последующие методы в списке не выполняются, а происходит поиск обработчиков в объемлющих делегат блоках;

· попытка вызвать делегат, в списке которого нет ни одного метода, вызывает генерацию исключения System.NullReferenceException.

Паттерн "наблюдатель"

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

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

Программисты часто используют одну и ту же схему организации и взаимодействия объектов в разных контекстах. За такими схемами закрепилось название паттерны, или шаблоны проектирования. Описанная стратегия известна под названием паттерн "наблюдатель".

Наблюдатель (observer) определяет между объектами зависимость типа "один ко многим", так что при изменении состояния одного объекта все зависящие от него объекты получают извещение и автоматически обновляются. Рассмотрим пример (листинг 2), в котором демонстрируется схема оповещения источником трех наблюдателей. Гипотетическое изменение состояния объекта моделируется сообщением "ОЙ!". Один из методов в демонстрационных целях сделан статическим.

using System;

namespace ConsoleApplication1

{

public delegate void Del(object o);         // объявление делегата

class Subj                                        // класс-источник

{

   Del dels;                     // объявление экземпляра делегата

   public void Register(Del d)           // регистрация делегата

   {

       dels += d;

   }

   public void OOPS()                          // что-то произошло

   {

       Console.WriteLine("ОЙ!");

       if (dels!= null) dels(this); // оповещение наблюдателей

   }

}

class ObsA                                     // класс-наблюдатель

{

   public void Do(object o)      // реакция на событие источника

   {

       Console.WriteLine("Бедняжка!");

   }

}

class ObsB                                     // класс-наблюдатель

{

   public static void See(object o) // реакция на событие источника

   {

       Console.WriteLine("Да ну, ерунда!");

   }

}

class Class1

{

   static void Main()

   {

       Subj s = new Subj();           // объект класса-источника

       ObsA o1 = new ObsA();           //             объекты

       ObsA o2 = new ObsA();           //  класса-наблюдателя

       s.Register(new Del(o1.Do)); // регистрация методов

       s.Register(new Del(o2.Do)); // наблюдателей в источнике

       s.Register(new Del(ObsB.See)); // (экземпляры делегата)

       s.OOPS();                       // инициирование события

   }

}

}

Листинг 2. Оповещение наблюдателей с помощью делегата

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

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

ОЙ!

Бедняжка!

Бедняжка!

Да ну, ерунда!

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

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

public void UnRegister(Del d)            // удаление делегата

{

dels -= d;

}

Операции

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

С делегатами одного типа можно выполнять операции простого и сложного присваивания, например:

Del d1 = new Del(o1.Do); // o1.Do

Del d2 = new Del(o2.Do); // o2.Do

Del d3 = d1 + d2;          // o1.Do и o2.Do

d3 += d1;                  // o1.Do, o2.Do и o1.Do

d3 -= d2;                  // o1.Do и o1.Do

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

Делегат, как и строка string, является неизменяемым типом данных, поэтому при любом изменении создается новый экземпляр, а старый впоследствии удаляется сборщиком мусора.

Передача делегатов в методы

Поскольку делегат является классом, его можно передавать в методы в качестве параметра. Таким образом обеспечивается функциональная параметризация: в метод можно передавать не только различные данные, но и различные функции их обработки. Функциональная параметризация применяется для создания универсальных методов и обеспечения возможности обратного вызова.

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

using System;

namespace ConsoleApplication1

{

public delegate double Fun(double x);      // объявление делегата

class Class1

{

   public static void Table(Fun F, double x, double b)

   {

       Console.WriteLine(" ----- X ----- Y -----");

       while (x <= b)

       {

          Console.WriteLine("| {0,8:0.000} | {1,8:0.000} |", x, F(x));

          x += 1;

       }

       Console.WriteLine(" ---------------------");

   }

   public static double Simple(double x)

  {

       return 1;

   }

       

   static void Main()

   {

       Console.WriteLine(" Таблица функции Sin ");

       Table(new Fun(Math.Sin), -2, 2);

       Console.WriteLine(" Таблица функции Simple ");

      Table(new Fun(Simple), 0, 3);

   }

}

}

Листинг 3. Передача делегата через список параметров

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

Таблица функции Sin ----- X ----- Y -----| -2,000 | -0,909 || -1,000 | -0,841 || 0,000 | 0,000 ||    1,000 | 0,841 || 2,000 | 0,909 | --------------------- Таблица функции Simple ----- X ----- Y -----| 0,000 | 1,000 || 1,000 | 1,000 || 2,000 | 1,000 || 3,000 | 1,000 | ---------------------

Обратный вызов (callback) представляет собой вызов функции, передаваемой в другую функцию в качестве параметра. Рассмотрим рисунок 1. Допустим, в библиотеке описана функция А, параметром которой является имя другой функции. В вызывающем коде описывается функция с требуемой сигнатурой (В) и передается в функцию А. Выполнение функции А приводит к вызову В, то есть управление передается из библиотечной функции обратно в вызывающий код.


Рис. 1. Механизм обратного вызова

Механизм обратного вызова широко используется в программировании. Например, он реализуется во многих стандартных функциях Windows.

Начиная с Visual Studio 2005, использующей версию 2.0 языка C#, можно применять упрощенный синтаксис для делегатов. Первое упрощение заключается в том, что в большинстве случаев явным образом создавать экземпляр делегата не требуется, поскольку он создается автоматически по контексту. Второе упрощение заключается в возможности создания так называемых анонимных методов — фрагментов кода, описываемых непосредственно в том месте, где используется делегат:

static void Main()

   {

       Console.WriteLine(" Таблица функции Sin ");

       Table(Math.Sin, -2, 2);                    // упрощение 1

       Console.WriteLine(" Таблица функции Simple ");

       Table(delegate (double x){ return 1; }, 0, 3); // упрощение 2

   }

}

}

В первом случае экземпляр делегата, соответствующего функции Sin, создается автоматически. Чтобы это могло произойти, список параметров и тип возвращаемого значения функции должны быть совместимы с делегатом. Во втором случае не требуется оформлять простой фрагмент кода в виде отдельной функции Simple, как это было сделано в предыдущем листинге, — код функции оформляется как анонимный метод и встраивается прямо в место передачи.

События

Событие — это элемент класса, позволяющий ему посылать другим объектам уведомления об изменении своего состояния. При этом для объектов, являющихся наблюдателями события, активизируются методы-обработчики этого события. Обработчики должны быть зарегистрированы в объекте-источнике события. Таким образом, механизм событий формализует на языковом уровне паттерн "наблюдатель", который рассматривался в предыдущем разделе.

Механизм событий можно также описать с помощью модели "публикация — подписка": один класс, являющийся отправителем (sender) сообщения, публикует события, которые он может инициировать, а другие классы, являющиеся получателями (receivers) сообщения, подписываются на получение этих событий.

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

· описание делегата, задающего сигнатуру обработчиков событий;

· описание события;

· описание метода (методов), инициирующих событие.

Синтаксис события похож на синтаксис делегата:

[ атрибуты ] [ спецификаторы ] event тип имя_события

Для событий применяются спецификаторы new, public, protected, internal, private, static, virtual, sealed, override, abstract и extern. Например, так же как и методы, событие может быть статическим (static), тогда оно связано с классом в целом, или обычным — в этом случае оно связано с экземпляром класса. Тип события — это тип делегата, на котором основано событие.

Пример описания делегата и соответствующего ему события:

public delegate void Del(object o);            // объявление делегата

class A

{

public event Del Oops;                        // объявление события

...

}

Обработка событий выполняется в классах-получателях сообщения. Для этого в них описываются методы-обработчики событий, сигнатура которых соответствует типу делегата. Каждый объект (не класс!), желающий получать сообщение, должен зарегистрировать в объекте-отправителе этот метод.

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

В листинге.4 приведен код из листинга  2, переработанный с использованием событий, а рисунок 2 поясняет организацию обработки событий.


Рис. 2. Выполнение программы с двумя нулевыми коэффициентами

using System;

namespace ConsoleApplication1

{

public delegate void Del();                  // объявление делегата

class Subj                                        // класс-источник

{

   public event Del Oops;                    // объявление события

   public void CryOops()            // метод, инициирующий событие

   {

       Console.WriteLine("ОЙ!");

       if (Oops!= null) Oops();

   }

}

class ObsA                                     // класс-наблюдатель

{

   public void Do();               // реакция на событие источника

   {

       Console.WriteLine("Бедняжка!");

   }

}

class ObsB                                     // класс-наблюдатель

{

   public static void See()        // реакция на событие источника

   {

       Console.WriteLine("Да ну, ерунда!");

   }

}

class Class1

{

   static void Main()

   {

       Subj s = new Subj();         // объект класса-источника

       ObsA o1 = new ObsA();         //               объекты

       ObsA o2 = new ObsA();         //    класса-наблюдателя

       s.Oops += new Del(o1.Do);   //            добавление

       s.Oops += new Del(o2.Do);   //          обработчиков

       s.Oops += new Del(ObsB.See); //             к событию

       s.CryOops();                  // инициирование события

   }

}

}

Листинг 4. Оповещение наблюдателей с помощью событий

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

Внутри класса, в котором описано событие, с ним можно обращаться, как с обычным полем, имеющим тип делегата: использовать операции отношения, присваивания и т. д. Значение события по умолчанию — null.

В библиотеке. NET описано огромное количество стандартных делегатов, предназначенных для реализации механизма обработки событий. Большинство этих классов оформлено по одним и тем же правилам:

· имя делегата заканчивается суффиксом EventHandler;

· делегат получает два параметра:

o первый параметр задает источник события и имеет тип object;

o второй параметр задает аргументы события и имеет тип EventArgs или производный от него.

Если обработчикам события требуется специфическая информация о событии, то для этого создают класс, производный от стандартного класса EventArgs, и добавляют в него необходимую информацию. Если делегат не использует такую информацию, можно обойтись стандартным классом делегата System.EventHandler.

Имя обработчика события принято составлять из префикса On и имени события. В листинге 5 приведен пример из листинга  4, оформленный в соответствии со стандартными соглашениями. NET.

using System;

namespace ConsoleApplication1

{

class Subj

{

   public event EventHandler Oops;

   public void CryOops()

   {

       Console.WriteLine("ОЙ!");

       if (Oops!= null) Oops(this, null);

   }

}

class ObsA

{

   public void OnOops(object sender, EventArgs e)

   {

       Console.WriteLine("Бедняжка!");

   }

}

class ObsB

{

   public static void OnOops(object sender, EventArgs e)

   {

       Console.WriteLine("Да ну, ерунда!");

   }

}

class Class1

{ static void Main()

   {

       Subj s = new Subj();

       ObsA o1 = new ObsA();

       ObsA o2 = new ObsA();

       s.Oops += new EventHandler(o1.OnOops);

       s.Oops += new EventHandler(o2.OnOops);

       s.Oops += new EventHandler(ObsB.OnOops);

       s.CryOops();

   }

}

}

Листинг 5. Использование стандартного делегата EventHandler

Многопоточные приложения

Приложение. NET состоит из одного или нескольких процессов. Процессу принадлежат выделенная для него область оперативной памяти и ресурсы. Каждый процесс может состоять из нескольких доменов (частей) приложения, ресурсы которых изолированы друг от друга. В рамках домена может быть запущено несколько потоков выполнения. Поток (thread) представляет собой часть исполняемого кода программы. Иногда термин " thread " переводится буквально — "нить", чтобы отличить его от потоков ввода-вывода. Поэтому в литературе можно встретить и термин "многонитевые приложения".

В каждом процессе есть первичный поток, исполняющий роль точки входа в приложение. Для консольных приложений это метод Main.

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

Недостатки многопоточности:

большое количество потоков ведет к увеличению накладных расходов, связанных с переключением потоков, что снижает общую производительность;

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

Класс Thread

В.NET многопоточность поддерживается в основном с помощью пространства имен System.Threading. Некоторые типы этого пространства описаны в таблице 1.

Таблица 10.1. Некоторые типы пространства имен System.Threading

Тип Описание
Monitor Класс, обеспечивающий синхронизацию доступа к объектам
Mutex Класс-примитив синхронизации, который используется также для синхронизации между процессами
Thread Класс, который создает поток, устанавливает его приоритет, получает информацию о состоянии
ThreadPool Класс, используемый для управления набором взаимосвязанных потоков — пулом потоков
Timer Класс, определяющий механизм вызова заданного метода в заданные интервалы времени для пула потоков
WaitHandle Класс, инкапсулирующий объекты синхронизации, которые ожидают доступа к разделяемым ресурсам
ThreadStart Делегат, представляющий метод, который должен быть выполнен при запуске потока
TimerCallback Делегат, представляющий метод, обрабатывающий вызовы от класса Timer
WaitCallback Делегат, представляющий метод для элементов класса ThreadPool
ThreadPriority Перечисление, описывающее приоритет потока
ThreadState Перечисление, описывающее состояние потока

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

Thread t = new Thread (new ThreadStart(имя_метода));

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

using System;

using System.Threading;

namespace ConsoleApplication1

{ class Program

{

   static public void Hedgehog()    // метод для вторичного потока

   {

       for (int i = 0; i < 6; ++i)

       {

           Console.Write(" " + i); Thread.Sleep(1000);

       }

   }

   

   static void Main()

   {

       Console.WriteLine("Первичный поток " +

           Thread.CurrentThread.GetHashCode());

      Thread ta = new Thread(new ThreadStart(Hedgehog));

       Console.WriteLine("Вторичный поток " + ta.GetHashCode());

       ta.Start();

       for (int i = 0; i > -6; --i)

       {

           Console.Write(" " + i); Thread.Sleep(400);

       }

   }

}

}

Листинг  6. Создание вторичного потока

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

Первичный поток 1

Вторичный поток 2

 0 0 -1 -2 1 -3 -4 2 -5 3 4 5

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

В таблице  2 перечислены основные элементы класса Thread.

Таблица 10.2. Основные элементы класса Thread

Элемент Вид Описание
CurrentThread Статическое свойство Возвращает ссылку на выполняющийся поток (только для чтения)
Name Свойство Установка текстового имени потока
Priority Свойство Получить/установить приоритет потока (используются значения перечисления ThreadPriority)
ThreadState Свойство Возвращает состояние потока (используются значения перечисления ThreadState)
Abort Метод Генерирует исключение ThreadAbortException. Вызов этого метода обычно завершает работу потока
Sleep Статический метод Приостанавливает выполнение текущего потока на заданное количество миллисекунд
Interrupt Метод Прерывает работу текущего потока
Join Метод Блокирует вызывающий поток до завершения другого потока или указанного промежутка времени и завершает поток
Resume Метод Возобновляет работу после приостановки потока
Start Метод Начинает выполнение потока, определенного делегатом ThreadStart
Suspend Метод Приостанавливает выполнение потока. Если выполнение потока уже приостановлено, то игнорируется

Можно создать несколько потоков, которые будут совместно использовать один и тот же код. Пример приведен в листинге   7.

using System;


Поделиться с друзьями:

Наброски и зарисовки растений, плодов, цветов: Освоить конструктивное построение структуры дерева через зарисовки отдельных деревьев, группы деревьев...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.337 с.