Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Применение методов экземпляра в качестве делегатов

Поиск

 

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

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

// Создаём делегат

delegate int IntOperation(int i, int j);

 

public class MyClass

{

public static int Sum(int x, int y)

{

return x + y;

}

 

public static int Prz(int x, int y)

{

return x * y;

}

 

public static int Del(int x, int y)

{

return x / y;

}

}

 

class Program

{

static void Main()

{

// Инициализируем делегат

IntOperation op1 = MyClass.Sum;

int result = op1(5, 10);

Console.WriteLine("Сумма: " + result);

op1 = MyClass.Prz;

result = op1(5, 10);

Console.WriteLine("Произведение: " + result);

Console.WriteLine("Для продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

/* Выведет:

* Сумма: 15

* Произведение: 50

* Для продолжения нажмите любую клавишу...

*/

 

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

 

Групповая адресация

 

Одним из самых примечательных свойств делегата является поддержка групповой адресации. Попросту говоря, групповая адресация — это возможность создать список, или цепочку вызовов, для методов, которые вызываются автоматически при обращении к делегату. Создать такую цепочку нетрудно. Для этого достаточно получить экземпляр делегата, а затем добавить методы в цепочку с помощью оператора (+) или (+=). Для удаления метода из цепочки служит оператор (-) или (-=). Если делегат возвращает значение, то им становится значение, возвращаемое последним методом в списке вызовов. Поэтому делегат, в котором используется групповая адресация, обычно имеет возвращаемый тип void. Пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

delegate void OpStroke(ref int[] arr);

 

public class ArrOperation

{

public static void WriteArray(ref int[] arr)

{

Console.WriteLine("Исходный массив: ");

foreach (int i in arr)

Console.Write("{0}\t", i);

Console.WriteLine();

}

// Сортировка массива

public static void IncSort(ref int[] arr)

{

int j, k;

 

for (int i = 0; i < arr.Length - 1; i++)

{

j = 0;

 

do

{

 

if (arr[j] > arr[j + 1])

{

k = arr[j];

arr[j] = arr[j + 1];

arr[j + 1] = k;

}

j++;

}

while (j < arr.Length - 1);

}

Console.WriteLine("Отсортированный массив в большую сторону: ");

foreach (int i in arr)

Console.Write("{0}\t", i);

Console.WriteLine();

}

 

public static void DecSort(ref int[] arr)

{

int j, k;

 

for (int i = 0; i < arr.Length - 1; i++)

{

j = 0;

 

do

{

 

if (arr[j] < arr[j + 1])

{

k = arr[j];

arr[j] = arr[j + 1];

arr[j + 1] = k;

}

j++;

}

while (j < arr.Length - 1);

}

Console.WriteLine("Отсортированный массив в меньшую сторону: ");

foreach (int i in arr)

Console.Write("{0}\t", i);

Console.WriteLine();

}

 

// Заменяем нечетные числа четными и наоборот

public static void ChetArr(ref int[] arr)

{

Console.WriteLine("Чётный массив: ");

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

if (arr[i] % 2!= 0)

arr[i] += 1;

foreach (int i in arr)

Console.Write("{0}\t", i);

Console.WriteLine();

}

 

public static void NeChetArr(ref int[] arr)

{

Console.WriteLine("Нечётный массив: ");

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

if (arr[i] % 2 == 0)

arr[i] += 1;

foreach (int i in arr)

Console.Write("{0}\t", i);

Console.WriteLine();

}

}

 

class Program

{

static void Main()

{

int[] myArr = new int[6] { 2, -4, 10, 5, -6, 9 };

 

// Структуируем делегаты

OpStroke Del;

OpStroke Wr = ArrOperation.WriteArray;

OpStroke OnSortArr = ArrOperation.IncSort;

OpStroke OffSortArr = ArrOperation.DecSort;

OpStroke ChArr = ArrOperation.ChetArr;

OpStroke NeChArr = ArrOperation.NeChetArr;

// Групповая адресация

Del = Wr;

Del += OnSortArr;

Del += ChArr;

Del += OffSortArr;

Del += NeChArr;

// Выполняем делегат

Del(ref myArr);

Console.WriteLine("Для продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 6. 1. Результат работы кода выше

 

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

3.11.2. Делегаты: ковариантность и контравариантность делегатов

Делегаты: ковариантность и контравариантность делегатов

 

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

 

Ниже приведён пример, демонстрирующий ковариантность и контравариантность:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

delegate Name UI(FamilyAndName obj);

 

public class Name

{

public string myName;

}

 

public class FamilyAndName: Name

{

public string Family;

}

 

public class UserInfo

{

public static Name UIName(Name obj)

{

obj.myName = "Имя пользователя: \"" + obj.myName + "\"";

return obj;

}

 

public static FamilyAndName UIFamilyName(FamilyAndName obj)

{

obj.Family = "Имя и фамилия: \"" + obj.myName + " " + obj.Family + "\"";

return obj;

}

}

 

class Program

{

static void Main()

{

// Пример ковариантности

// Можно использовать метод, возвращаемым типом параметра которого

// является производный класс

UI user1 = UserInfo.UIFamilyName;

// Пример контравариантности

// Можно использовать метод, аргументом которого является

// базовый класс

user1 = UserInfo.UIName;

}

}

}

 

Ковариантность и контравариантность делегатов находит довольно широкое применение при реализации событий.

3.11.3. Делегаты: «Action<T>» и «Func<T>»

Делегаты: «Action<T>» и «Func<T>»

 

Вместо определения в C# нового типа делегата с каждым типом параметра и возврата можно использовать делегаты «Action<T>» и «Func<T>».

Обобщённый делегат Action<T> предназначен для ссылки на метод, возвращающий void. Этот класс делегата существует в различных вариантах, так что ему можно передавать до 16 разных типов параметров.

Сам по себе класс Action без обобщённого параметра предназначен для вызова методов без параметров, Action<in Т> — для вызова метода с одним параметром, Action<in T1, in Т2> —для вызова метода с двумя параметрами и Action<in T1, in Т2, in ТЗ, in Т4, in Т5, in Т6, in Т7, in Т8> — для вызова метода с восемью параметрами.

Делегаты Func<T> могут использоваться аналогичным образом. Func<T> позволяет вызывать методы с типом возврата. Подобно Action<T>, Func<T> определён в разных вариантах для передачи до 16 типов параметров и типа возврата. Func<out TResult> — тип делегата для вызова метода с типом возврата, но без параметров, Func<in T1, out TResult> — для метода с одним параметром, a Func<in T1, in T2, in ТЗ, in T4, out TResult> — для метода с четырьмя параметрами.

 

Рассмотрим пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class ArrSort

{

// Реализуем обобщенный метод сортировки

static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> res)

{

bool mySort = true;

 

do

{

mySort = false;

for (int i = 0; i < sortArray.Count - 1; i++)

{

 

if (res(sortArray[i + 1], sortArray[i]))

{

T j = sortArray[i];

sortArray[i] = sortArray[i + 1];

sortArray[i + 1] = j;

mySort = true;

}

}

}

while (mySort);

}

}

 

class UserInfo

{

public string Name { get; private set; }

public string Family { get; private set; }

public decimal Salary { get; private set; }

 

public UserInfo(string Name, string Family, decimal Salary)

{

this.Name = Name;

this.Family = Family;

this.Salary = Salary;

}

// Переопределяем метод ToString

public override string ToString()

{

return string.Format("{0} {1}, {2:C}", Name, Family, Salary);

}

// Данный метод введён для соответствия сигнатуре

// делегата Func

public static bool UserSalary(UserInfo obj1, UserInfo obj2)

{

return obj1.Salary < obj2.Salary;

}

}

 

class Program

{

static void Main()

{

UserInfo[] userinfo = { new UserInfo("Dmitry","Medvedev", 50000000000),

new UserInfo("John","A.", 100),

new UserInfo("Denis","R.", 40000),

new UserInfo("Roman","E.", 1000000)};

ArrSort.Sort(userinfo, UserInfo.UserSalary);

Console.WriteLine("Сортируем исходный объект по доходу:\n" +

"------------------------------------\n");

foreach (var ui in userinfo)

Console.WriteLine(ui);

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 1. 1. Результат работы кода выше

Делегаты: анонимные методы

Делегаты: анонимные методы

 

Метод, на который ссылается делега т, нередко используется только для этой цели. Иными словами, единственным основанием для существования метода служит то обстоятельство, что он может быть вызван посредством делегата, но сам он не вызывается вообще. В подобных случаях можно воспользоваться анонимной функцией, чтобы не создавать отдельный метод. Анонимная функция, по существу, представляет собой безымянный кодовый блок, передаваемый конструктору делегата. Преимущество анонимной функции состоит, в частности, в ее простоте. Благодаря ей отпадает необходимость объявлять отдельный метод, единственное назначение которого состоит в том, что он передается делегату.

Начиная с версии C# 3.0, в С# предусмотрены две разновидности анонимных функций - анонимные методы и лямбда-выражения:

 

Рис. 1. 1. Разновидности анонимных функций

 

Анонимные методы были внедрены в С# ещё в версии C# 2.0, а лямбда-выражения — в версии 3.0. В целом лямбда-выражение совершенствует принцип действия анонимного метода и в настоящее время считается более предпочтительным для создания анонимной функции. Но анонимные методы широко применяются в существующем коде С# и поэтому по-прежнему являются важной составной частью С#. А поскольку анонимные методы предшествовали появлению лямбда-выражений, то ясное представление о них позволяет лучше понять особенности лямбда-выражений. К тому же анонимные методы могут быть использованы в целом ряде случаев, где применение лямбда-выражений оказывается невозможным.

Анонимный метод — один из способов создания безымянного блока кода, связанного с конкретным экземпляром делегата. Для создания анонимного метода достаточно указать кодовый блок после ключевого слова delegate. Покажем, как это делается, на конкретном примере:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

delegate int Sum(int number);

 

class Program

{

static Sum SomeVar()

{

int result = 0;

// Вызов анонимного метода

Sum del = delegate(int number)

{

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

result += i;

return result;

};

return del;

}

 

static void Main()

{

Sum del1 = SomeVar();

for (int i = 1; i <= 5; i++)

{

Console.WriteLine("Cумма {0} равна: {1}", i, del1(i));

}

Console.WriteLine("Для продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 1. 2. Результат работы кода выше

 

Обратим внимание на получившийся результат. Локальная переменная, в область действия которой входит анонимный метод, называется внешней переменной. Такие переменные доступны для использования в анонимном методе. И в этом случае внешняя переменная считается захваченной. Захваченная переменная существует до тех пор, пока захвативший её делегат не будет собран в «мусор». Поэтому если локальная переменная, которая обычно прекращает свое существование после выхода из кодового блока, используется в анонимном методе, то она продолжает существовать до тех пор, пока не будет уничтожен делегат, ссылающийся на этот метод.

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

Понятие «Лямбда-выражение»

Понятие «Лямбда-выражение»

 

Понятие «Лямбда-выражение»

 

Начиная с С# 3.0, доступен новый синтаксис для назначения реализации кода делегатам, называемый лямбда-выражениями (lambda expression). Лямбда-выражения могут использоваться везде, где есть параметр типа делегата.

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

Во всех лямбда-выражениях применяется лямбда-оператор (=>), который разделяет лямбда-выражение на две части. В левой его части указывается входной параметр (или несколько параметров), а в правой части — тело лямбда-выражения. Оператор «=>» иногда описывается такими словами, как «переходит» или «становится».

В С# поддерживаются две разновидности лямбда-выражений в зависимости от тела самого лямбда-выражения. Так, если тело лямбда-выражения состоит из одного выражения, то образуется одиночное лямбда-выражение. В этом случае тело выражения не заключается в фигурные скобки. Если же тело лямбда-выражения состоит из блока операторов, заключенных в фигурные скобки, то образуется блочное лямбда-выражение.При этом блочное лямбда-выражение может содержать целый ряд операторов, в том числе циклы, вызовы методов и условные операторы if. Обе разновидности лямбда-выражений рассматриваются далее по отдельности.

 

Одиночные лямбда-выражения

 

В одиночном лямбда-выражении часть, находящаяся справа от оператора «=>», воздействует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения лямбда-оператора. Ниже приведена общая форма одиночного лямбда-выражения, принимающего единственный параметр:

 

<параметр> => <выражение>

 

Если же требуется указать несколько параметров, то используется следующая форма:

 

<список параметров> => <выражение>

 

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

 

Лямбда-выражение применяется в два этапа. Сначала объявляется тип делегата, совместимый с лямбда-выражением, а затем экземпляр делегата, которому присваивается лямбда-выражение. После этого лямбда-выражение вычисляется при обращении к экземпляру делегата. Результатом его вычисления становится возвращаемое значение. Давайте рассмотрим пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

// Создадём несколько делегатов имитирующих

// простейшую форму регистрации

delegate int LengthLogin (string s);

delegate bool BoolPassword (string s1, string s2);

 

class Program

{

static void Main()

{

link1:

Console.Write("Введите имя пользователя: ");

string login = Console.ReadLine();

// Используем лямбда-выражение

LengthLogin ll = ss => ss.Length;

 

int longlogin = ll(login);

if (longlogin >= 25)

{

Console.WriteLine("\nСлишком длинное имя.\n");

goto link1;

}

Console.Write("Введите пароль: ");

string password1 = Console.ReadLine();

Console.Write("Повторите пароль: ");

string password2 = Console.ReadLine();

// Используем лямбда-выражение

BoolPassword bp = (s1, s2) => s1 == s2;

if (bp(password1, password2))

Console.WriteLine("\nРегистрация завершена.");

else

Console.WriteLine("\nРегистрация не завершена. Пароли не совпадают.");

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 2. 1. Результат работы кода выше

 

Блочные лямбда-выражения

 

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

 

Давайте модифицируем предыдущий пример, добавив «капчу» (защитное поле используемое зачастую на проверку «а не робот ли пользователь?») в форму регистрации:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

// Создадём несколько делегатов имитирующих

// простейшую форму регистрации

delegate int LengthLogin(string s);

delegate bool BoolPassword(string s1, string s2);

delegate void Captha(string s1, string s2);

 

class Program

{

static void Main()

{

link1:

Console.Write("Введите логин: ");

string login = Console.ReadLine();

// Используем лямбда-выражение

LengthLogin ll = ss => ss.Length;

 

int longlogin = ll(login);

if (longlogin >= 25)

{

Console.WriteLine("\nСлишком длинное имя.\n");

goto link1;

}

 

Console.Write("Введите пароль: ");

string password1 = Console.ReadLine();

Console.Write("Повторите пароль: ");

string password2 = Console.ReadLine();

 

// Используем лямбда выражение

BoolPassword bp = (s1, s2) => s1 == s2;

 

if (bp(password1, password2))

{

Random ran = new Random();

string resCaptha = "";

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

resCaptha += (char)ran.Next(0, 100);

Console.WriteLine("Введите код: " + resCaptha);

string resCode = Console.ReadLine();

// Реализуем блочное лямбда-выражение

Captha cp = (s1, s2) =>

{

if (s1 == s2)

Console.WriteLine("\nРегистрация завершена.");

else

Console.WriteLine("\nРегистрация не завершена.");

return;

};

cp(resCaptha, resCode);

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

}

 

Рис. 3. 1. Результат работы кода выше

Понятие «Событие»

Понятие «Событие»

 

Понятие «Событие»

 

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

 

События являются членами класса и объявляются с помощью ключевого слова event. Чаще всего для этой цели используется следующая форма:

 

event <делегат события> <имя события>;

 

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

 

События основаны на делегатах и предоставляют им механизм публикации/подписки. В каркасе .NET события присутствуют повсюду. В приложениях Windows Forms, например, класс Button поддерживает событие Click. Этот тип события является делегатом. Метод-обработчик, вызываемый с событием Click, должен быть определен с параметрами, заданными в типе делегата.

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

Методы экземпляра и статические методы могут быть использованы в качестве обработчиков событий, но между ними имеется одно существенное отличие. Когда статический метод используется в качестве обработчика, уведомление о событии распространяется на весь класс. А когда в качестве обработчика используется метод экземпляра, то события адресуются конкретным экземплярам объектов. Следовательно, каждый объект определённого класса, которому требуется получить уведомление о событии, должен быть зарегистрирован отдельно. На практике большинство обработчиков событий представляют собой методы экземпляра, хотя это, конечно, зависит от конкретного приложения.

 

Рассмотрим пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

delegate void UI();

 

class MyEvent

{

// Объявляем событие

public event UI UserEvent;

 

// Используем метод для запуска события

public void OnUserEvent()

{

UserEvent();

}

}

 

class UserInfo

{

string uiName, uiFamily;

int uiAge;

 

public UserInfo(string Name, string Family, int Age)

{

this.Name = Name;

this.Family = Family;

this.Age = Age;

}

 

public string Name { set { uiName = value; } get { return uiName; } }

public string Family { set { uiFamily = value; } get { return uiFamily; } }

public int Age { set { uiAge = value; } get { return uiAge; } }

// Обработчик события

public void UserInfoHandler()

{

Console.WriteLine("Событие вызвано.\n");

Console.WriteLine("Имя: {0}\nФамилия: {1}\nВозраст: {2}", Name, Family, Age);

}

}

 

class Program

{

static void Main()

{

MyEvent evt = new MyEvent();

UserInfo user1 = new UserInfo(Name: "Vladimir", Family: "Putin", Age: 59);

// Добавляем обработчик события

evt.UserEvent += user1.UserInfoHandler;

// Запустим событие

evt.OnUserEvent();

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 1. 1. Результат работы кода выше

 

Как видим, в данном примере создаётся событие UserEvent, являющееся членом делегата UI. Обработчик данного события определяется в классе UserInfo, и добавляется с помощью синтаксиса «+=».

 

Событие С# в действительности развертывается в два скрытых метода, один из которых имеет префикс add_, а другой — remove_. За этим префиксом следует имя события С#. Например, событие UserEvent превращается в два скрытых метода CIL с именами add_UserEvent и remove_UserEvent. Если заглянуть в CIL-код метода add_UserInfoHandler, можно обнаружить там вызов метода Delegate.Combine.

 

Аксессоры» событий

 

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

 

event <делегат события> <имя события>

{

add {

// Код добавления события в цепочку событий

}

remove

{

// Код удаления события из цепочки событий

}

 

В эту форму входят два аксессора событий: add и remove. Аксессор add вызывается, когда обработчик событий добавляется в цепочку событий с помощью оператора +=. В то же времяаксессор remove вызывается, когда обработчик событий удаляется из цепочки событий с помощью оператора -=.

Когда вызывается аксессор add или remove, он принимает в качестве параметра добавляемый или удаляемый обработчик. Как и в других разновидностях аксессоров, этот неявный параметр называется value. Реализовав аксессоры add или remove, можно организовать специальную схему хранения обработчиков событий. Например, обработчики событий можно хранить в массиве, стеке или очереди.

 

Вышесказанное иллюстрирует нижеследующее:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

delegate void UI ();

 

class MyEvent

{

UI[] evnt = new UI[5];

 

// Объявляем событие

public event UI UserEvent

{

// Используем аксессоры событий

add

{

evnt[1] = value;

}

 

remove

{

evnt[1] = null;

}

}

// Используем метод для запуска события

public void OnUserEvent()

{

evnt[1]();

}

}

 

class UserInfo

{

string uiName, uiFamily;

int uiAge;

 

public UserInfo(string Name, string Family, int Age)

{

this.Name = Name;

this.Family = Family;

this.Age = Age;

}

 

public string Name { set { uiName = value; } get { return uiName; } }

public string Family { set { uiFamily = value; } get { return uiFamily; } }

public int Age { set { uiAge = value; } get { return uiAge; } }

// Обработчик события

public void UserInfoHandler()

{

Console.WriteLine("Событие вызвано.\n");

Console.WriteLine("Имя: {0}\nФамилия: {1}\nВозраст: {2}",Name,Family,Age);

}

}

 

class Program

{

static void Main()

{

MyEvent evt = new MyEvent();

UserInfo user1 = new UserInfo(Name: "Vladimir", Family: "Putin", Age: 59);

// Добавляем обработчик события

evt.UserEvent += user1.UserInfoHandler;

// Запускаем событие

evt.OnUserEvent();

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");

Console.ReadKey();

}

}

}

 

Рис. 2. 1. Результат работы кода выше

 

Длинная нотация для определения событий удобна, если необходимо сделать нечто большее, чем просто добавлять и удалять обработчики событий, например, добавить синхронизацию для многопоточного доступа. Элементы управления WPF используют длинную нотацию для добавления функциональности «пузырькового» и туннельного распространения событий.

3.11.7. События: пример обработки событий

События: пример обработки событий

 

В С# разрешается формировать какие угодно разновидности событий. Но ради совместимости программных компонентов со средой .NET Framework следует придерживаться рекомендаций, установленных для этой цели корпорацией Microsoft. Эти рекомендации, по существу, сводятся к следующему требованию: у обработчиков событий должны быть два параметра. Первый из них — ссылка на объект, формирующий событие, второй — параметр типа EventArgs, содержащий любую дополнительную информацию о событии, которая требуется обработчику. Таким образом,.NET-совместимые обработчики событий должны иметь следующую общую форму:

 

void <обработчик>(object <отправитель>, EventArgs е) {

//...

}

 

Как правило, отправитель — это параметр, передаваемый вызывающим кодом с помощью ключевого слова this. А параметр е типа EventArgs содержит дополнительную информацию о событии и может быть проигнорирован, если он не нужен.

Сам класс EventArgs не содержит поля, которые могут быть использованы для передачи дополнительных данных обработчику. Напротив, EventArgs служит в качестве базового класса, от которого получается производный класс, содержащий все необходимые поля. Тем не менее, в классе EventArgs имеется одно поле Empty типа static, которое представляет собой объект типа EventArgs без данных.

В среде.NET Framework предоставляется встроенный обобщенный делегат под названием EventHandler<TEventArgs>. В данном случае тип TEventArgs обозначает тип аргумента, передаваемого параметру EventArgs события.

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

Ниже приведен пример программы, в которой формируется.NET-совместимое событие:

 

// Реализуем программу реагирующую на события

// нажатия клавиш - вызывающих специализированные команды консоли

using System;

 

namespace LC_Console

{

// Производный класс от EventArgs

class MyEventArgs: EventArgs

{

public char ch;

}

 

public class KeyEvent

{

// Создаём событие, используя обобщенный делегат

public event EventHandler KeyDown;

public char ch1;

 

public void OnKeyDown(char ch)

{

MyEventArgs c = new MyEventArgs();

 

if (KeyDown!= null)

{

c.ch = ch;

ch1 = ch;

KeyDown(this, c);

}

}

}

 

class Program

{

 

static void Main()

{

KeyEvent evnt = new KeyEvent();

evnt.KeyDown += (sender, e) =>

{

 

switch (evnt.ch1)

{

case 'C':

{

MyColor(true);

break;

}

case 'B':

{

MyColor(false);

break;

}

case 'S':

{

Console.Write("\nВведите длину: ");

try

{

int Width = int.Parse(Console.ReadLine()) / 8;

Console.Write("Введите ширину: ");

int Height = int.Parse(Console.ReadLine()) / 8;

Console.WindowWidth = Width;

Console.WindowHeight = Height;

Console.WriteLine();

}

catch (FormatException)

{

Console.WriteLine("Неверный формат.");

}

catch (ArgumentOutOfRangeException)

{

Console.WriteLine("Окно настолько не растянется.");

}

break;

}

case 'T':

{

Console.Write("\nВведите новый заголовок: ");

string s = Console.ReadLine();

Console.Title = s;

Console.WriteLine();

break;

}

case 'R':

{

Console.ForegroundColor = ConsoleColor.White;

Console.BackgroundColor = ConsoleColor.Black;

Console.WriteLine();

break;

}

case 'E':

{

Console.Beep();

break;

}

default:

{

Console.WriteLine("\nТакая команда не найдена.");

break;

}

}

};

ConsoleTitle();

char ch;

 

do

{

Console.Write("Введите комманду: ");

ConsoleKeyInfo key;

key = Console.ReadKey();

ch = key.KeyChar;

evnt.OnKeyDown(key.KeyChar);

}

while (ch!= 'E');

}

 

// Несколько вспомогательных методов

static void ConsoleTitle()

{

CC(ConsoleColor.Green);

Console.WriteLine("***************************\n\nПрограмма настройки консоли"

+ "\n___________________________\n");

CC(ConsoleColor.Yellow);

Console.WriteLine("Управляющие команды: \n");

Command("C", "Поменять цвет текста");

Command("B", "Поменять цвет фона");

Command("S", "Поменять размер окна");

Command("T", "Поменять заголовок");

Command("R", "Сбросить изменения");

Command("E", "Выход");

Console.WriteLine();

}

 

static void CC(ConsoleColor color)

{

Console.ForegroundColor = color;

}

 

static void Command(string s1, string s2)

{

CC(ConsoleColor.Red);

Console.Write(s1);

CC(ConsoleColor.White);

Console.Write(" - " + s2 + "\n");

}

 

static void MyColor(bool F_or_B)

{

link1:

Console.Write("\nВведите цвет: ");

string s = Console.ReadLine();

switch (s)

{

case "Black":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Black;

else

Console.BackgroundColor = ConsoleColor.Black;

break;

}

case "Yellow":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Yellow;

else

Console.BackgroundColor = ConsoleColor.Yellow;

break;

}

case "Green":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Green;

else

Console.BackgroundColor = ConsoleColor.Green;

break;

}

case "Red":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Red;

else

Console.BackgroundColor = ConsoleColor.Red;

break;

}

case "Blue":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Blue;

else

Console.BackgroundColor = ConsoleColor.Blue;

break;

}

case "Gray":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.Gray;

else

Console.BackgroundColor = ConsoleColor.Gray;

break;

}

case "White":

{

if (F_or_B)

Console.ForegroundColor = ConsoleColor.White;

else

Console.BackgroundColor = ConsoleColor.White;

break;

}

default:

{

Console.WriteLine("Такой цвет не найден.");

goto link1;

}

}

Console.WriteLine("Цвет был изменён.");

}

}

}

 

Рис. 1. 1. Результат работы кода выше

 

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

ГЛАВА 4. РАСШИРЕННЫЕ ВОЗМОЖНОСТИ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ C#

4.1. Препроцессорные директивы в C#

Препроцессорные директивы в C#

 

Препроцессорные директивы представляют собой команды, которые изменяют код программы и, которые влияют на процесс сборки и выходной результат этой обработки. В компиляторах C и C++ имеется отдельный препроцессор, но в C# такового нет. Однако, компилятор может обрабатывать директивы условной компиляции, но даже их, нельзя использовать для создания макросов. Ещё одно условие — директива препроцессора должна быть единственной в строке.

 

Директива всегда начинается со знака «#». После этого знака следует имя директивы.

 

Рассмотрим директивы, которые можно использовать в C# на примере директив для условной компиляции:

 

#if — используется вместе с директивой #endif. Описывает условие, при котором код между двумя этими командами будет выполнен. Символ, который следует за #if, должен иметь тип Boolean. Например:

 

#define DEBUG

//...

#if DEBUG

// Компилируется какой-то код

#endif

 

Если значение DEBUG будет true, код внутри блока, будет скомпилирован. Значение DEBUG будет равен true, только если оно был ранее определён. За это отвечает #define, но о нём чуть позже.

Аналогично можно использовать так:

 

#define DEBUG

//...

#if (DEBUG == true)

// Компилируется какой-то код

#endif

 

Или так:

 

#define DEBUG

//...

#if (DEBUG && XXX)

// Компилируется какой-то код

#endif

 

Т.е. для проверки можно использовать операторы && и ||, а также группировать с помощью скобок.

 

#else — директива следует за #if и является аналогом else в блоке условий if:

 

#define DEBUG

//...

#if (DEBUG && XXX)

// Компилируется какой-то код, если условие верно

#else

// Если условие не верно, то компилируется это код

#endif

 

За #else всегда следует #endif.

 

#elif — аналог else if. Используется для создания сложных условных конструкций. С помощью #elif можно организовать switch:

 

#if DEBUG

// Какой-то код

#elif XXX

// Какой-то код

#else

// Здесь компилируется код, если два условия выше были false

#endif

 

#elif, также как и #if необходимо задать условие. Если условие не выполняется, то код внутри директивы не будет учитываться при компиляции.

 

#endif — просто закрывает #if. Если #elif и #else могут и не описываться, то #endif обязательно должен быть описан.

 

#define — определяет значение. Определённый, таким образом, символ будет возвращать true в директиве #if. Смотрим пример для #if.

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

 

Полноценный использования вышеописанной директивы #if:

 

// preprocessor_if.cs

#define DEBUG

#define MYTEST

 

using System;

 

public class Program

{

static void Main()

{

#if (DEBUG &&!MYTEST)

Console.WriteLine("DEBUG объявлен");

#elif (!DEBUG && MYTEST)

Console.WriteLine("MYTEST объявлен");

#elif (DEBUG && MYTEST)

Console.WriteLine("DEBUG и MYTEST объявлены");

Console.WriteLine("Для продолжения нажмите любую клавишу... ");

Console.ReadKey();

#else

Console.WriteLine("DEBUG and MYTEST не объявл



Поделиться:


Последнее изменение этой страницы: 2016-08-15; просмотров: 602; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.14.249.191 (0.015 с.)