События Лекция 06. 11. 12 г. 1
Общие сведения о событиях Событие – это один из видов членов типа. Если в типе определен член-событие, то этот тип (или его экземпляр) может уведомлять другие объекты о некоторых особых событиях. Например, класс Button ( «кнопка» ) определяет событие Click ( «щелчок» ). В приложении имеются объекты, которые должны получать уведомление о щелчке объекта Button, и, получив такое уведомление, исполнять некоторые действия. Тип, в котором определены события, как минимум поддерживает: Ø регистрацию статического метода типа или экземплярного метода объекта, заинтересованных в получении уведомления о событии; Ø отмену регистрации статического метода типа или экземплярного метода объекта, получающих уведомления о событии; Ø уведомление зарегистрированных методов о том, что событие произошло. Лекция 06. 11. 12 г. 2
Делегаты Модель событий CLR основана на делегатах (delegate). Делегаты позволяют обращаться к методам обратного вызова (callback method), не нарушая безопасности типов. Метод обратного вызова — это механизм, позволяющий объекту получать уведомления, на которые он подписался. Техника функций обратного вызова (callback functions) широко применяется в программировании для Windows. Функции обратного вызова – это указатели на функции, через которые могут быть вызваны функции. В. Net концепция указателей на функции реализована в форме делегатов, которые безопасны в отношении типов. Основное назначение делегатов – реализация механизма событий. Лекция 06. 11. 12 г. 3
Делегаты Пример объявления и использования делегата: public delegate void D(int k); class Program { D – имя типа static void f(int n) { Console. Write. Line("nf(): n={0}", n); } static void Main(string[] args) { D d = new D(f); // создание и инициализ. экземпляра делегата d(5); // вызов функции f через делегат Console. Read. Line(); } } d=f Синтаксически делегат подобен классу: сначала дается определение делегата (как типа), а затем создаются экземпляры делегата, которые с помощью конструктора делегата связываются с совпадающими по сигнатуре функциями. Объявление делегата можно поместить в любое место, где может находиться объявление класса. Лекция 06. 11. 12 г. 4
Делегаты Один и тот же экземпляр делегата можно связывать с разными функциями: public delegate void D(int k); class Program { static void f(int n) { Console. Write. Line("nf(): n={0}", n); } static void f 1(int n) { Console. Write. Line("nf 1(): n={0}", n); } static void Main(string[] args) { D d = new D(f); // создание и инициализация делегата d(5); // вызов функции f через делегат d = f 1; // «прикрепление» к другой функции d(9); // вызов функции f 1 через делегат Console. Read. Line(); } } Лекция 06. 11. 12 г. 5
Делегаты Экземпляр делегата может ссылаться на любой метод – уровня экземпляра или статический – любого объекта любого типа, если сигнатура этого метода совпадает с сигнатурой делегата: public delegate void D(int k); class A { public void f. D(int x) { // функция экземпляра Console. Write. Line("nf. D(): x={0}", x); } public static void fs. D(int x) { // функция класса Console. Write. Line("nfs. D(): x={0}", x); } } class Program {. . . static void Main(string[] args) {. . . d = A. fs. D; d(13); // fs. D(): x=13 A a = new A(); d = a. f. D; d(21); // f. D(): x=21 Console. Read. Key(); } } Лекция 06. 11. 12 г. 6
Предположение делегата. Массивы экземпляров делегата Синтаксис языка C# допускает инициализировать экземпляр делегата без явного вызова конструктора, непосредственно именем метода: public delegate void D(int k); … D d = f; // вместо D d = new D(f); Экземпляры делегата могут быть помещены в массив: public delegate void D(int k); . . . D[] md = { f, f 1, A. fs. D, a. f. D }; int v = 4; foreach (D x in md) x(v++); Результат: f(): n=4 f 1(): n=5 fs. D(): x=6 f. D(): x=7 Лекция 06. 11. 12 г. 7
Анонимные методы Экземпляр делегата можно связать с еще не существующей функцией, которая создается «на лету» . Такая функция называется анонимным методом: public delegate void D(int k); … D d = delegate(int n) { Console. Write. Line("n. Anonym(): n={0}", n); }; d(5); // Anonym(): n=5 d = delegate(int n) { Console. Write. Line("n. Anonym 1(): n={0}", n); }; d(9); // Anonym 1(): n=9 Лекция 06. 11. 12 г. 8
Групповые делегаты В рассмотренных примерах экземпляр делегата являлся в одно и то же время «оболочкой» только для одного, конкретного метода. При необходимости можно было «переприкрепить» к нему другой метод. В языке C# существует возможность рассматривать экземпляр делегата как контейнер, в который помещены сразу несколько методов. Такие делегаты называются групповыми (multicast delegates). При вызове группового делегата последовательно вызываются все находящиеся в нем методы. Ограничение: делегат может быть групповым, если он имеет тип возвращаемого значения void. К групповым делегатам применимы операции +, -, += и -=. Операндами могут являться как групповые делегаты, так и имена методов. Групповые делегаты рассматриваются как мультимножества, а операции + и – как операции «объединение» и «пересечение» для мультимножеств. Лекция 06. 11. 12 г. 9
Групповые делегаты //Ex 105. cs using System; public delegate void D(ref int k); class A { public void f. D(ref int n) { // функция экземпляра Console. Write. Line("f. D() : n={0}", n++); } public static void fs. D(ref int n) { // функция класса Console. Write. Line("fs. D(): n={0}", n++); } } class Program { static void f(ref int n) { Console. Write. Line("f() : n={0}", n++); } static void f 1(ref int n) { Console. Write. Line("f 1() : n={0}", n++); } static void Main(string[] args) { D d = f; d += f 1; // d={f, f 1} D d 1 = A. fs. D; A a = new A(); d 1 += a. f. D; d 1 += f; // d 1={A. fs. D, a. f. D, f} D d 2 = d + d 1; // d 2={f, f 1, A. fs. D, a. f. D, f} int v = 1; d 2(ref v); Console. Write. Line("--------"); d 2 = d 2 -f-f 1; // d 2={f, A. fs. D, a. f. D} d 2(ref v); Console. Read. Line(); } } Лекция 06. 11. 12 г. 10
Групповые делегаты и массивы делегатов Делегаты реализованы как классы, унаследованные от класса System. Multicast. Delegate, который наследуется от базового класса System. Delegate. В этом классе определен метод Get. Invocation. List(), позволяющий преобразовать групповой делегат в массив делегатов: public delegate void D(ref int k); . . . static void Main(string[] args) {. . . int v = 5; d 2(ref v); d 2 = d 2 -f-f 1; // d 2={f, A. fs. D, a. f. D} d 2(ref v); Delegate[] md = d 2. Get. Invocation. List(); foreach (D i in md) i(ref v); Лекция 06. 11. 12 г. 11
Делегаты и события Понятие события. Приложения Windows управляются сообщениями, т. е. взаимодействие приложения и ОС ведется посредством обмена предопределенными сообщениями (порциями информации, структурами данных). В. Net сообщения Windows «обертываются» в события. События используются в качестве посредников в коммуникациях между объектами, а делегаты – в качестве средства формирования событий. Роли отправителя и получателя событий: Ø отправитель генерирует событие, ничего не зная о получателях этого события; он определяет делегат, который будет использован получателем; Ø получатель определяет обработчик события и «прикрепляет» его к событию (выполняет привязку события). Отправителем может быть объект, сборка или исполняющая система. Net (для системных событий). Лекция 06. 11. 12 г. 12
Делегаты и события //Ex 107. cs using System; public delegate void D(object s); // делегат public class E { // класс - генератор событий public event D E 1, E 2; // события public void g(int k) { if (k<10) E 1(this); else E 2(this); } } class Program { static void f(object s) { // обработчик 1 Console. Write. Line("Function f() was called by {0}. ", s. Get. Type()); } static void f 1(object s) { // обработчик 2 Console. Write. Line("Function f 1() was called by {0}. ", s. Get. Type()); } static void Main(string[] args) { E x = new E(); // Создаём экземпляр класса - генератора событий x. E 1 += f; // привязываем обработчик к событию x. E 2 += f 1; // привязываем обработчик к событию for(int k = 8; k < 12; k++) x. g(k); Console. Read. Line(); } } Лекция 06. 11. 12 г. 13
Делегаты и события Приведенный пример демонстрирует события в «чистом» виде. В большинстве случаев событие сопровождается «сообщением» , содержащим дополнительную информацию для получателя. Роль носителя сообщения может выполнять объект любого типа, например, int. Лекция 06. 11. 12 г. 14
Делегаты и события //Ex 108. cs using System; public delegate void D(object s, int n); // делегат public class E { // класс - генератор событий public event D E 1, E 2; // события public void g(int k) { if (k<10) E 1(this, k); else E 2(this, k); } } class Program { static void f(object s, int n) { // обработчик 1 Console. Write. Line("Function f() was called by {0}. n = {1}", s. Get. Type(), n); } static void f 1(object s, int n) { // обработчик 2 Console. Write. Line("Function f 1() was called by {0}. n = {1}", s. Get. Type(), n); } static void Main(string[] args) { E x = new E(); // Создаём экземпляр класса - генератора событий x. E 1 += f; // привязываем обработчик к событию x. E 2 += f 1; // привязываем обработчик к событию for(int k = 8; k < 12; k++) x. g(k); Console. Read. Line(); } } Лекция 06. 11. 12 г. 15
Делегаты и события Использование объектов простых типов в качестве носителей сообщений неудобно в силу их низкой выразительности. Более подходящими для этих целей являются объекты - экземпляры классов, которые к тому же позволяют организовать двусторонний обмен сообщениями: получатель может изменить поля в сообщении и это может проанализировать отправитель. Лекция 06. 11. 12 г. 16
Делегаты и события //Ex 109. cs using System; public class Msg { // класс сообщений public int a; } public delegate void D(Object s, Msg e); // делегат public class E { // класс - генератор событий public event D E 1, E 2; // события public Msg z = new Msg(); public void g(int k) { z. a = k; if (k < 10) E 1(this, z); else E 2(this, z); } } class Program { static void f(object s, Msg n) { // обработчик 1 Console. Write. Line("Function f() was called by {0}. n={1}", s. Get. Type(), n. a); n. a += 10; } static void f 1(object s, Msg n) { // обработчик 2 Console. Write. Line("Function f 1() was called by {0}. n={1}", s. Get. Type(), n. a); n. a += 100; } static void Main(string[] args) { E x = new E(); // Создаём экземпляр класса - генератора событий x. E 1 += f; // привязываем обработчик 1 к событию x. E 2 += f 1; // привязываем обработчик 2 к событию for(int k = 8; k < 12; k++) { x. g(k); Console. Write. Line("x. z. a={0}", x. z. a); } Console. Read. Line(); } } Лекция 06. 11. 12 г. 17
Делегаты и события Принято использовать в качестве носителей сообщений экземпляры системного класса System. Event. Args или классов, производных от него (их существует более 100). Часто создают свой класс, непосредственно унаследованный от Event. Args. public class Msg : Event. Args { // класс сообщений public int a; } Кроме того, в целях унификации принято использовать непосредственно класс Event. Args в тех случаях, когда не требуется формировать и использовать сообщения. В этом случае сигнатура делегата будет такой: public delegate void D(object s, Event. Args e); Большинство системных событий используют именно эту форму. Для всех событий, которые не используют дополнительную информацию, в. Net уже определен соответствующий делегат - System. Event. Handler public delegate void Event. Handler(object sender, Event. Args e); Лекция 06. 11. 12 г. 18
Делегаты и события Пример простого приложения Win. Forms, созданного в Notepad++ //Ex 115. cs using System; using System. Windows. Forms; public class Sample. Form : Form { private Button button 1 = new Button(); public static void Main (String[] args) { Application. Run(new Sample. Form()); } public Sample. Form() { this. Text = "Делегаты и события"; button 1. Text = "Событие"; button 1. Click += new System. Event. Handler(button. Clicked); this. Controls. Add(button 1); } private void button. Clicked(object sender, Event. Args ev. Args) { Message. Box. Show("Кнопка была нажата"); } } Командная строка для компиляции и создания приложения: %net%csc /t: winexe /r: System. dll; System. Windows. Forms. dll Ex 115. cs Лекция 06. 11. 12 г. 19
Делегаты и события Пример простого приложения Win. Forms, созданного в Visual Studio. Создадим форму Form 1 с элементами button 1, panel 1 и panel 2. Будем использовать 3 события, связанных с button 1: Click (нажать кнопку), Mouse. Down (ЛКМ нажата) и Mose. Up (ЛКМ отпущена), а также 3 соответствующих обработчика: f, f 1 и f 2: Лекция 06. 11. 12 г. 20
Делегаты и события //Form 1. cs namespace Windows. Forms. Application 1 { public partial class Form 1 : Form { public Form 1() { Initialize. Component(); } private bool b = true; private void f(object sender, Event. Args e) { if (b) { b = false; panel 1. Back. Color = System. Drawing. Color. Red; } else { b = true; panel 1. Back. Color = System. Drawing. Color. Blue; } } private void f 1(object sender, Mouse. Event. Args e) { panel 2. Back. Color = System. Drawing. Color. Blue; } private void f 2(object sender, Mouse. Event. Args e) { panel 2. Back. Color = System. Drawing. Color. Red; } } } Лекция 06. 11. 12 г. 21
Делегаты и события //Form 1. Designer. cs namespace Windows. Forms. Application 1 { public partial class Form 1 { private void Initialize. Component() {. . . this. button 1. Click += new System. Event. Handler(this. f); this. button 1. Mouse. Down += new System. Windows. Forms. Mouse. Event. Handler(this. f 1); this. button 1. Mouse. Up += new System. Windows. Forms. Mouse. Event. Handler(this. f 2); } } } Лекция 06. 11. 12 г. 22
Делегаты и события Форма после запуска, затем ЛКМ нажата и отпущена: Затем повторно ЛКМ нажата и отпущена: Лекция 06. 11. 12 г. 23
Обобщения Лекция 06. 11. 12 г. 24
Понятие обобщения Концептуально обобщения похожи на шаблоны языка С++, но существует принципиальное различие между ними: инстанцирование (конкретизация, настройка) шаблонов выполняется статически, во время компиляции, а инстанцирование обобщений происходит динамически, во время JITкомпиляции. Шаблоны невозможно поместить в библиотеки, т. к. исходный код шаблона класса необходим при создании конкретного класса и поэтому весь код шаблонов обычно находится в заголовочных файлах, что затрудняет защиту авторских прав. Обобщения определены для CLR и поэтому могут упаковываться в сборки и в таком виде «потребляться» позднее, причем не обязательно на том же языке, на котором написано обобщение (например, обобщенный класс C# может использоваться для создания конкретного класса на языке Visual Basic). В результате становится возможным повторное использование двоичного кода. Лекция 06. 11. 12 г. 25
Понятие обобщения Обобщения C#, как и шаблоны C++, являются новой формой повторного использования кода, а именно повторным использованием алгоритма. Разработчик определяет алгоритм, например сортировку, поиск, замену, сравнение или преобразование, но не указывает типы данных, с которыми тот работает. Поэтому алгоритм может обобщенно применяться к объектам разных типов. Используя готовый алгоритм, другой разработчик должен указать конкретные типы данных, например для алгоритма сортировки — Int 32 s, String и т. д. , а для алгоритма сравнения — Date. Time, Versions. . . Большинство алгоритмов инкапсулированы в типе. CLR поддерживает создание как обобщенных ссылочных, так и обобщенных значимых типов. Кроме того, CLR позволяет создавать обобщенные интерфейсы и обобщенные делегаты. Иногда полезный алгоритм инкапсулирован в одном методе, поэтому CLR поддерживает создание обобщенных методов, определенных в ссылочном, значимом типе или в интерфейсе. Лекция 06. 11. 12 г. 26
Понятие обобщения Например, в библиотеке FCL определен обобщенный алгоритм управления списками, работающий с набором объектов произвольного типа. Для использования этого алгоритма нужно указывать конкретные типы данных. FCLкласс, инкапсулирующий обобщенный алгоритм управления списками, называется List<T> и определен в пространстве имен System. Collections. Generic. Исходный текст для определения этого класса выглядит примерно так: public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable { public List(); public void Add(T item); public Int 32 Binary. Search(T item); public void Clear(); public Boolean Contains(T item); public Int 32 Index. Of(T item); public Boolean Remove(T item); public void Sort(IComparer<T> comparer); public void Sort(Comparison<T> comparison); public T[] To. Array(); public Int 32 Count { get; } public T this[Int 32 index] { get; set; } } Лекция 06. 11. 12 г. 27
Понятие обобщения Пример использования обобщённого типа List<T>: //Ex 112. cs using System; using System. Collections. Generic; class Program { private static void Some. Method() { // Создание списка (List), работающего с объектами Date. Time. List<Date. Time> dt. List = new List<Date. Time>(); // Добавление объекта Date. Time в список. dt. List. Add(Date. Time. Now); // Без упаковки. // Добавление еще одного объекта Date. Time в список. dt. List. Add(Date. Time. Min. Value); // Без упаковки. // Попытка добавить объект типа String в список. //dt. List. Add("1/1/2004"); // Ошибка при компиляции. // Извлечение объектов Date. Time из списка. Console. Write. Line("{0}n{1}", dt. List[0], dt. List[1]); } static void Main() { Some. Method(); Console. Read. Line(); } } Лекция 06. 11. 12 г. 28
Понятие обобщения Основные преимущества обобщений: 1. Повышение производительности. 2. Безопасность типов. 3. Возможность повторного использования двоичного кода. Повышение производительности происходит за счет отказа от операций упаковки (boxing) и распаковки (unboxing) в тех случаях, когда они не нужны. Пример программирования без обобщений. Используется класс Array. List из пространства имен System. Collections, который хранит элементы типа object. Array. List list = new Array. List(); list. Add(44); // число 44 упаковывается в object list. Add(98); // число 98 упаковывается в object int i = (int)list[0]; // распаковка foreach (int k in list) Console. Write. Line(k); // распаковка Лекция 06. 11. 12 г. 29
Понятие обобщения Пример программирования с использованием обобщений: List<int> list = new List<int>(); list. Add(44); // число 44 не упаковывается в object list. Add(98); // число 98 не упаковывается в object int i = list[0]; // распаковки нет foreach (int k in list) Console. Write. Line(k); // распаковки нет В этом примере в качестве фактического параметра обобщенного класса указан int и поэтому внутри динамически сгенерированного JIT-компилятором конкретного класса List<int> используется поле типа int, для которого упаковка и распаковка не нужны. Лекция 06. 11. 12 г. 30
Понятие обобщения Второе преимущество от использования обобщений – обеспечение безопасности типов за счет точного указания типа для элементов коллекции. Пример программирования без обобщений: используется класс Array. List, который хранит элементы типа object и допускает вставку в коллекцию элементов различных типов, что может привести к ошибкам времени выполнения. public class A { } static void Main(string[] args) { Array. List list = new Array. List(); list. Add(44); // число 44 упаковывается в object list. Add("abcd"); // строка "abcd" упаковывается в object list. Add(new A()); foreach (int k in list) // ошибка времени выполнения Console. Write. Line(k); Console. Read. Line(); } Лекция 06. 11. 12 г. 31
Понятие обобщения Пример программирования с использованием обобщённого класса List<T>: public class A { } static void Main(string[] args) { List<int> list = new List<int>(); list. Add(44); list. Add("abcd"); // ошибка компиляции list. Add(new A()); // ошибка компиляции. . . } Лекция 06. 11. 12 г. 32
Понятие обобщения Пример: создание класса для связного списка (программирование без обобщений). Linked. List. Node null Linked. List. Node Add. Last(object node) IEnumerator Get. Enumerator() Лекция 06. 11. 12 г. 33
Понятие обобщения Исходный код класса Linked. List. Node для узла списка: public class Linked. List. Node { public Linked. List. Node(object value) { this. value = value; } private object value; public object Value { get { return value; } } private Linked. List. Node next; public Linked. List. Node Next { get { return next; } set { next = value; } } private Linked. List. Node prev; public Linked. List. Node Prev { get { return prev; } set { prev = value; } } } Лекция 06. 11. 12 г. 34
Понятие обобщения Исходный код класса Linked. List для списка: public class Linked. List : IEnumerable { private Linked. List. Node first; public Linked. List. Node First {get { return first; }} private Linked. List. Node last; public Linked. List. Node Last {get { return last; }} public Linked. List. Node Add. Last(object node) { Linked. List. Node new. Node = new Linked. List. Node(node); if (first == null) {first = new. Node; last = first; } else {last. Next = new. Node; last = new. Node; } return new. Node; } public IEnumerator Get. Enumerator() { Linked. List. Node current = first; while (current != null){ yield return current. Value; current = current. Next; } } } Лекция 06. 11. 12 г. 35
Понятие обобщения Исходный код тестовой программы для списка, реализованного обычным классом: Linked. List list 1 = new Linked. List(); list 1. Add. Last(1); list 1. Add. Last(2); list 1. Add. Last("3"); foreach (int i in list 1) { // ошибка времени выполнения Console. Write. Line(i); } Лекция 06. 11. 12 г. 36
Понятие обобщения Пример 2. Создание обобщенного класса для связного списка. Исходный код обобщенного класса Linked. List. Node<T> для узла списка: public class Linked. List. Node<T> { public Linked. List. Node(T value) { this. value = value; } private T value; public T Value { get { return value; } } private Linked. List. Node<T> next; public Linked. List. Node<T> Next { get { return next; } set { next = value; } } private Linked. List. Node<T> prev; public Linked. List. Node<T> Prev { get { return prev; } set { prev = value; } } } Лекция 06. 11. 12 г. 37
Понятие обобщения Исходный код обобщенного класса Linked. List<T> для списка: public class Linked. List<T> : IEnumerable<T> { private Linked. List. Node<T> first; public Linked. List. Node<T> First {get { return first; }} private Linked. List. Node<T> last; public Linked. List. Node<T> Last {get { return last; }} public Linked. List. Node<T> Add. Last(T node) { Linked. List. Node<T> new. Node = new Linked. List. Node<T>(node); if (first == null) {first = new. Node; last = first; } else {last. Next = new. Node; last = new. Node; } return new. Node; } public IEnumerator<T> Get. Enumerator() { Linked. List. Node<T> current = first; while (current != null){ yield return current. Value; current = current. Next; } } IEnumerator IEnumerable. Get. Enumerator() { return Get. Enumerator(); } } Лекция 06. 11. 12 г. 38
Понятие обобщения Исходный код тестовой программы для списка, реализованного обобщенным классом: //Ex 113. cs using System; using System. Collections. Generic; public class Linked. List. Node<T> {. . . } public class Linked. List<T> : IEnumerable<T> {. . . } class Program { static void Main() { Linked. List<string> list 3 = new Linked. List<string>(); list 3. Add. Last("abc"); list 3. Add. Last("de"); list 3. Add. Last(13); // ошибка компиляции list 3. Add. Last("fghik"); foreach (string s in list 3) Console. Write. Line(s); Console. Read. Line(); } } Лекция 06. 11. 12 г. 39
Понятие обобщения Выводы: 1. Каждый класс, имеющий дело с данными типа object, является кандидатом на обобщенную реализацию. 2. В некоторых случаях обобщения позволяют избавиться от иерархии классов и исключить необходимость в приведении типов. Лекция 06. 11. 12 г. 40
Дополнительные синтаксические конструкции для обобщенных классов 1. Значения по умолчанию. В исходном коде обобщенного класса используются объекты обобщенного типа T (например, T x). Может появиться необходимость присвоить таким объектам значение «по умолчанию» . Т. к. в языке C# существует два значения «по умолчанию» : число 0 (для типов значений) и константа null (для ссылочных типов), невозможно произвести такое присваивание для x однозначно. Оба присваивания: и x=0, и x=null будут неверными. Для подобных случаев имеется специальное ключевое слово default, которое используется так: T x = default(T); // верно! Лекция 06. 11. 12 г. 41
Дополнительные синтаксические конструкции для обобщенных классов 2. Ограничения. Если в исходном коде обобщенного класса необходимо вызывать методы из обобщенного типа T, то необходимо быть уверенным, что эти методы определены для типа T. Подобное требование к типу T называется ограничением. Существуют следующие виды ограничений: where T : A Т должен быть типа A, или типа, производного от А where T : struct Т должен быть типом значений where T : class Т должен быть ссылочным where T : IA Т должен реализовывать интерфейс IA where T : new() Т должен иметь конструктор по умолчанию Лекция 06. 11. 12 г. 42
Дополнительные синтаксические конструкции для обобщенных классов Порядок перечисления ограничений: Без ограничений С ограничениями ↓ ↓ ↓ Без разделителей class My. Class < T 1, T 2, T 3 > ↓ where T 2: Customer // Ограничение для T 2 where T 3: IComparable // Ограничение для T 3 { ↑. . . Без разделителей } Список ограничений ↓ ↓ where Type. Param : ограничение, . . . ↑ Двоеточие Лекция 06. 11. 12 г. 43
Дополнительные синтаксические конструкции для обобщенных классов Примеры ограничений: class Sorted. List<S> where S: IComparable<S> {. . . } class Linked. List<M, N> where M : IComparable<M> where N : ICloneable {. . . } class My. Dictionary<Key. Type, Value. Type> where Key. Type : IEnumerable, new() {. . . } class Employee. List<T> where T : Employee, IEmployee, new(), System. IComparable<T> {. . . } class My. Classy<T, U> where T : class where U : struct {. . . } public static void Op. Test<T>(T s, T t) where T : class {. . . } class List<T> { void Add<U>(List<U> items) where U : T {. . . } } public class Sample. Class<T, U, V> where T : V {. . . } Лекция 06. 11. 12 г. 44
Дополнительные синтаксические конструкции для обобщенных классов 3. Наследование для обобщенных классов. Обобщенный класс может реализовывать обобщенный интерфейс или наследовать от обобщенного базового класса: public class B<T> { } public class D<T> : B<T>, IEnumerable<T> { } При этом должно выполняться требование, чтобы и в производном, и в базовых классах использовалось одно и тоже имя для обобщенного типа T, или обобщенный базовый класс должен быть инстанцирован (преобразован к «закрытому» классу): public class B<T> { } public class D<T> : B<string>, IEnumerable<T> { } Лекция 06. 11. 12 г. 45
Дополнительные синтаксические конструкции для обобщенных классов 4. Статические члены обобщенных классов. Если обобщенный класс имеет в своем составе статическое поле, то это поле «дублируется» для каждого закрытого класса, полученного путем инстанцирования обобщенного класса: public class Static. Demo<T> { // открытый класс public static int x; }. . . Static. Demo<string>. x = 4; // закрытый класс Static. Demo<int>. x = 5; // закрытый класс Console. Write. Line(Static. Demo<string>. x); // 4 Лекция 06. 11. 12 г. 46
Обобщенные интерфейсы В языке C# помимо обобщенных классов существуют: - обобщенные интерфейсы; - обобщенные методы; - обобщенные делегаты. Обобщенные интерфейсы. Можно определять интерфейсы, объявляющие методы с обобщенными параметрами: public interface IComparable<T> { int Compare. To(T other); } Такая возможность позволяет избежать дополнительных преобразования между object и конкретным типом: public class Person : IComparable { public int Compare. To(object x) { Person p = x as Person; return this. lastname. Compare. To(p. lastname); } } public class Person: IComparable<Person>{ public int Compare. To(Person p) { return this. lastname. Compare. To(p. lastname); } } Лекция 06. 11. 12 г. 47
Обобщенные методы В объявлении обобщенного метода присутствует обобщенный тип: void Swap<T>(ref T x, ref T y) { T temp = x; x = y; y = temp; } Вызов обобщенного методы производится одним из способов: int i = 4, j = 7; Swap<int>(ref i, ref j); // явное указание конкретного типа Swap(ref i, ref j); // конкретный тип выводится из типа аргумента Лекция 06. 11. 12 г. 48
Обобщенные методы Далее приведен более содержательный пример, в котором в контейнер типа My. Container<long>, полученного из обобщенного класса My. Container<T>, добавляется содержимое другого контейнера типа My. Container<int>, также полученного из обобщенного класса My. Container<T>. Обобщенный класс My. Container<T> содержит обобщенный метод Add<R>. //Ex 114. cs //public delegate TOutput Converter<in TInput, out TOutput>(TInput input) using System; using System. Collections. Generic; public class My. Container<T> : IEnumerable<T> { public void Add(T item) {impl. Add(item); } public void Add<R>(My. Container<R> other. Container, Converter<R, T> converter) { // делегат! foreach (R item in other. Container) impl. Add(converter(item)); } public IEnumerator<T> Get. Enumerator() { foreach (T item in impl) yield return item; } IEnumerator IEnumerable. Get. Enumerator(){ return Get. Enumerator(); } private List<T> impl = new List<T>(); } Лекция 06. 11. 12 г. 49
Обобщенные методы Продолжение public class Program { static void Main() { My. Container<long> l. Container = new My. Container<long>(); My. Container<int> i. Container = new My. Container<int>(); l. Container. Add(1); l. Container. Add(2); i. Container. Add(3); i. Container. Add(4); // весь i. Container «вливается» в l. Container: l. Container. Add(i. Container, Int. To. Long. Converter); foreach (long y in l. Container) Console. Write. Line(y); Console. Read. Line(); } static long Int. To. Long. Converter(int i) { return i; } } Лекция 06. 11. 12 г. 50
Обобщенные делегаты В предыдущем примере был использован обобщенный делегат Converter<R, T>, который имеет следующее объявление (в пространстве имен System): public delegate TOutput Converter<in TInput, out TOutput>(TInput input) Для создания экземпляра обобщенного делегата можно использовать операцию new и явно указать список конкретных типов, или же использовать сокращенный синтаксис, когда компилятор сам выводит типы параметров (что и было сделано в предыдущем примере): l. Container. Add(i. Container, Int. To. Long. Converter); Напомним, что заголовок описания обобщенного метода Add<R> был таким: public void Add<R>(My. Container<R> other. Container, Converter<R, T> converter) Лекция 06. 11. 12 г. 51
Использование обобщенных делегатов с классами Array Ранее рассматривались приемы работы с классами Array с использованием интерфейсов IComparable и IComparer. Начиная с версии. Net 2. 0 некоторые методы класса Array используют обобщенные делегаты в качестве параметров. Метод Array. Sort() принимает в качестве параметра обобщенный делегат, выполняющий сравнение двух объектов: public static void Sort<T> (T[] array, Comparison<T> comparison) public delegate int Comparison<T>(T x, T y) Благодаря этому можно сортировать массив с помощью анонимного делегата: Person[] persons = {new Person(. . . ), . . . , new Person(. . . )}; Array. Sort(persons, delegate(Person p 1, Person p 2) { return p 1. Firstname. Compare. To(p 2. Firstname); } ); Лекция 06. 11. 12 г. 52