Купить
 
 
Жанр: Учеба

Си шарп: создание приложений для windows

страница №11

n this.Data;
-his.Data = value;
interface IValidate
{
bool Validate();
)
class MyControi: FancyControl, IValidate
(
public MyControi()
{
data = "таблица данных";
}
public bool Validate()
{
Console.WriteLine("Проверка...{0}", data)
return true;
174 Раздел I I . Фундаментальные понятия
class InterfaceApp
public static void MainO
{
MyControl myControl = new MyControl();
IValidate val = (IValidate)myControl;
bool success = val.Validate();
Console.WriteLine("Проверка '{О}1 (1} была завершена успешно",
myControl.data,
(true == success ? "": "не"));
С помощью определения такого класса и интерфейса редактор может
запрашивать у элемента управления, реализует ли он интерфейс IValidate
(ниже будет показано, как это сделать). Если это так, редактор может
проверить данный элемент управления, а затем вызывать реализованные
методы интерфейса. Вы можете спросить: "А почему бы мне просто
не определить базовый класс для использования в этом редакторе, у
которого есть чисто виртуальная функция Validate? Ведь после этого редактор
будет принимать только элементы управления, производные от
этого базового класса, да?"
Да, но... это решение повлечет суровые ограничения. Допустим, вы
создаете собственные элементы управления, каждый из которых является
производным от гипотетического базового класса. Как следствие, все
они реализуют виртуальный метод Validate. Это будет работать, пока в
один прекрасный день вы не найдете по-настоящему замечательный
элемент управления и вам не захочется использовать его в редакторе.
Допустим, вы нашли компонент "сетка", написанный кем-то другим и
поэтому не являющийся производным от нужного редактору базового
класса "элемент управления". На C++ решение в том, чтобы с помощью
множественного наследования сделать сетку производной от компонента
стороннего разработчика и базового класса редактора одновременно. Но
С# не поддерживает множественное наследование.
Интерфейсы позволяют реализовать несколько характеристик поведения
в одном классе. На С# можно создавать объекты, производные от
одного класса, и в дополнение к этой унаследованной функциональности
реализовать столько интерфейсов, сколько нужно для класса. Например,
чтобы приложение-редактор, проверяя содержимое элемента управления,
связывало элемент управления с базой данных и последовательно направляло
его содержимое на диск, объявите свой класс так:
public class MyGrid: ThirdPartyGrid, IValidate, ISeriallzable, IDataBound
И все же вопрос остался: "Как отдельный фрагмент кода узнает, реализован
ли классом данный интерфейс?"
Интерфейсы 175
Инструкция is
В предыдущем примере вы видели код, использованный для приведения
объекта (MyControl) к одному из реализованных в нем интерфейсов
(IValidate) и затем для вызова одного из членов этого интерфейса (Validate).
MyControl myControl = new MyControl ();
IValidate val= (IValidate) myControl;
bool Success = val.Vailidate();
Что будет, если клиент попытается использовать класс так, как если
бы в последнем был реализован метод, на самом деле в нем не реализованный?
Следующий пример будет скомпилирован, поскольку интерфейс
ISerializable является допустимым. И все же в период выполнения будет
передано исключение System.InvalidCastException, так как в MyGrid не реализован
интерфейс ISerializable. После этого выполнение приложения прервется,
если только исключение не будет явно уловлено.
using System;
public class FancyControl
{
protected string Data;
public string data
{
get
t
return this.Data;
this.Data = value;
interface ISerializable
i
bool Save();

1


interface IValidate
{
bool Validate();
}
class MyControl: FancyControl, IValidate
{
public MyControl()
176 Раздел I I . Фундаментальные понятия
data = "таблица данных";
public bool Validated
{
Console.WriteLine("Проверка... (О)", data);
return true;
class IsOperatorlApp
i
public static void Main()
{
MyControl myControl = new MyControl();
ISerializable ser =" (ISerializable)myControl;
//будет сгенерировано исключение
bool success = ser.Save ();
Console.WriteLine ("Сохранение '(О)' (1} завершено успешно",
myControl.data,
(true == success ? "": "не"));
Конечно, улавливание исключения не повлияет на то, что предназначенный
для выполнения код в этом случае не будет исполнен. Способ
запроса объекта перед попыткой его приведения - вот что вам нужно.
Один из способов - задействовать оператор is. Он позволяет в период
выполнения проверять совместимость одного типа с другим. Оператор
имеет следующий вид, где "выражение" - ссылочный тип:
выражение is тип
Результат оператора is - булево значение, которое затем можно использовать
с условными операторами. В следующем примере код был изменен,
чтобы проверять совместимость между классом MyControl и интерфейсом
ISerializable перед попыткой вызова метода ISerializable:
using System;
public class FancyControl
{
protected string Data;
public string data
Интерфейсы 177
get
(
return this.Data;
}
set
{
this.Data = value;
interface ISerializable
{
bool Save () ;
}
interface IValidate
{
bool Validate();
}
class MyControl: FancyControl, IValidate
{
public MyControl()
{
data = "таблица данных";
)
public bool Validated
{
Console.WriteLine("Проверка... {0}", data);
return true;
)
)
class IsOperatorlApp
{
public static void Main()
{
MyControl myControl = new MyControl();
if(myControl is ISerializable)
ISerializable ser = (ISerializable)myControl,
//будет сгенерировано исключение
178 Раздел XI. Фундаментальные понятия
bool success = ser.SaveO;
Console.WriteLine("Сохранение ' (0 } ' {1) завершено успешно
myControl.data,
(true ==• success ? "": "не")];
}
else
{
Console.WriteLine("Интерфейс ISerializable не реплизован")
После запуска приложения вы получите сообщение:
Интерфейс ISerializable не реализован
Вы увидели, как оператор is позволяет проверить совместимость двух
типов, чтобы гарантировать их корректное использование. А теперь рассмотрим
его близкого родственника - оператор as и сравним их.

Инструкция as
Код операции isinst, генерируемый компилятором для оператора С# is,
проверяет, чем является объект: экземпляром класса или интерфейсом.
И лишь после этого, при условии, что проверка условий пройдена, компилятор
генерирует код операции приведения типа и выполняет приведение
объекта к типу интерфейса. Операция приведения типа осуществляет
собственную проверку, и, поскольку она работает несколько иначе,
в результате сгенерированный код выполняет неэффективную работу,
дважды проверяя правильность приведения.
Мы можем повысить эффективность процесса проверки с помощью
оператора as, который преобразует совместимые типы и принимает такой
вид:
объект = выражение as тип,
где "выражение" - это любой ссылочный тип.
Можно думать, что оператор as представляет собой комбинацию оператора
is и, если рассматриваемые типы совместимы, приведения. Важное
различие между as и is в том, что если выражение и тип несовместимы,
то вместо возврата булева значения оператор as устанавливает
объект в null. Теперь наш пример можно переписать:
using System;
public class FancyControl
{
protected string Data;
public string data
f
get
Интерфейсы 179
return this.Data;
}
set
f
this.Data = value;
interface ISerializable
bool Save();
)
interface IValidate
bool Validate() ;
class MyControl: FancyControl, IValidate
public MyControl()
data = "таблица данных";
public bool Validate()
Console.WriteLine("Проверка... { 0}" , data);
return true;
)
)
class AsOperatorlApp
public static void Main()
f
KyControl myControl = new MyControl();
ISerializable ser = myControl as ISerializable;
if (ser != null!
//будет сгенерировано исключение
bool success = ser.Savef);
Console.WriteLine("Сохранение '{OS1 {1} завершено успешно'
180 Раздел I I . Фундаментальные понятия
myControl.data,
(true == success ? "": "не"));
}
else
"
Console.WriteLine("Интерфейс ISerializable не реализован");
Проверка, гарантирующая правильность приведения, производится
только раз, что, естественно, намного эффективнее.
ЯВНАЯ КВАЛИФИКАЦИЯ ИМЕНИ ЧЛЕНА ИНТЕРФЕЙСА
Вы уже познакомились с классами, реализующими интерфейсы путем
задания модификатора доступа public, за которым следует сигнатура метода
интерфейса. Однако иногда у вас будет возникать желание (или
даже необходимость) явно квалифицировать имя члена именем интерфейса.
В этом разделе мы изучим две причины, которые могут заставить
вас поступить таким образом.
Сокрытие имен с помощью интерфейсов
Чтобы вызвать метод, реализованный в интерфейсе, необходимо привести
экземпляр этого класса к типу интерфейса и вызвать нужный метод-
это самый распространенный подход. Хотя это работает и многие
используют данную методику, формально вы вовсе не обязаны приводить
объект к реализованному им интерфейсу, чтобы вызывать методы
этого интерфейса. Это так, потому что методы интерфейса, реализованные
классом, также являются открытыми методами класса. Взгляните
на код на С#, особенно на метод Main, чтобы понять, что я имею в виду:
using System;
public interface IDataBound
{
void Bind () ;
public class EditBox: IDataBound
{
//реализация IDataBound
public void Bind()
{
Console.WriteLine("Связывание с данными... " ) ;
\

1


Интерфейсы 181
class NaraeHiddinglApp
{
public static void Main()
{
Edi~Box edit = new EditBox();
Console.WriteLine("Вызов метода EditBox.Bind()");
edii.Bind();
Console.HriteLine();
IDataBound bound = (IDataBound)edit;
Console.WriteLine("Вызов метода (IDacaBound)EditBox.Bind(]")
bound.Bind(|;
Теперь этот пример выдаст следующее:
Вызов метода EditBox.BindQ
Связывание с данными...
Вызов метода (IDataBound)EditBox.BindQ
Связывание с данными...
Заметьте, хотя это приложение вызывает реализованный метод Bind
двумя способами - с помощью приведения и без него, оба вызова корректно
функционируют - Bind выполняется. Хотя поначалу возможность
прямого вызова реализованного метода без приведения объекта к интерфейсу
может показаться неплохой, порой это более чем нежелательно.
Самая очевидная причина в том, что реализация нескольких интерфейсов,
каждый из которых может содержать массу членов, может привести
к быстрому засорению открытого пространства имен вашего класса членами,
не имеющими значения за пределами видимости реализующего
эти интерфейсы класса. Вы можете не позволять реализованным членам
интерфейсов становиться открытыми членами класса, применяя методику
сокрытия имен (name hiding).
В простейшем случае сокрытие имен представляет собой возможность
скрывать имя унаследованного члена от любого кода за пределами
производного или реализующего его класса (его часто называют
внешним миром (outside world). Возьмем тот пример, где класс EditBox
должен был реализовывать интерфейс IDataBound, но на этот раз EditBox
не будет предоставлять методы IDataBound внешнему миру. Этот интерфейс
нужен ему скорее для собственных нужд, или, возможно, программист
просто не хочет засорять пространство имен класса многочисленными
методами, которые обычно не используются клиентом.
Чтобы скрыть член реализованного интерфейса, нужно лишь удалить
модификатор доступа члена public и квалифицировать имя члена именем
интерфейса:
182 Раздел I I . Фундаментальные понятия
using System;
public interface IDataBound
I

void Bind();
public class EditBoxi IDataBound
I

//реализация IDataBound
void IDataBound.Bind{)
{
Console.WriteLine("Связывание с данными... ")
class NameHiddinglApp
{
public static void Main()
{
EditBox edit - new EditBoxf),-
Console.WriteLine("Вызов метода EditBox.Bind(]");
// Ошибка
// EditBox больше не содержит в себе метод Bind
edit.Bind();
Console.WriteLine();
IDataBound bound = {IDataBound)edit;
Console.WriteLine("Вызов метода (IDataBound)EditBox.Bind()");
bound.Bind();
Этот код не будет компилироваться, так как имя члена Bind более не
является частью класса EditBox. Поэтому данная методика позволяет вам
удалять член из пространства имен класса, в то же время разрешая явный
доступ к нему с помощью операции приведения.
При сокрытии члена вы не можете применять модификатор доступа.
При попытке использования модификатора доступа с членом реализованного
интерфейса вы получите ошибку периода компиляции. Может,
это покажется странным, но поймите, что общая причина, заставляющая
скрыть что-то,- желание сделать эту сущность невидимой за пределами
текущего класса. Так как модификаторы доступа существуют лишь для
определения уровня видимости за пределами базового класса, при сокрытии
имен они не имеют смысла.

Интерфейсы 183
Избежание неоднозначности имен
Одна из главных причин, по которой С# не поддерживает множественное
наследование,- проблема конфликта имен, результатом которой является
неоднозначность имен. Хотя С# не поддерживает множественное
наследование на уровне объектов (создание производных классов), он
поддерживает наследование от одного класса и дополнительную реализацию
нескольких интерфейсов. Однако за дополнительные возможности
приходится расплачиваться конфликтами имен.
Ниже интерфейсы ISerializable и IDataStore поддерживают чтение и хранение
данных в разных форматах: в двоичной форме в виде объектов для
хранения на диске и для хранения в БД. Проблема в том, что оба содержат
методы с именем SaveData:
using System;
namespace NameCollisions
{
interface ISerializable
{
void SaveData();
i
interface IDataStore
{
void SaveData();
class Test: ISerializable, IDataStore
public void SaveDataO
Console.WriteLine("Test.SaveData() вызван");
}
class NameCollisionsApp
public static void Main()
Test test = new Test ();
Console.WriteLine("Вызов метода Test.SaveData()");
test.SaveData();
I

I

184 Раздел I I . Фундаментальные понятия
Независимо от того, компилируется ли этот код, у вас возникнет проблема,
так как поведение в результате вызова метода SaveData будет
неопределенным для программиста, пытающегося задействовать этот
класс. Получите ли вы метод SaveData, который последовательно записывает
данные объекта на диск, или метод SaveData, который сохраняет их
в БД? В дополнение взгляните на такой код:
using System;
namespace NameCollisions
interface ISerializable
void SaveData ();
)
interface IDataStore
void SaveData();
class Test: ISerializable, IDataStore
i
public void SaveData()
{
Console.WriteLine("Test.SaveData() вызван")
class NameCollisionsApp
{
public static void MainQ
{
Test test = new Test();
if (test is ISerializable)
(
//Console.WriteLine("Вызов метода Test.SaveData()");
Console.WriteLine("Интерфейс ISerializable реализуется")
//test.SaveData();
}
if{test is IDataStore)
{
//Console.WriteLine("Вызов метода Test.SaveData()");
Console.WriteLine("Интерфейс IDataStore реализуется");
//test.SaveData();

1


}
Интерфейсы 185
В этом примере оператор is успешно выполняется в обоих интерфейсах,
а значит, реализованы оба интерфейса, хотя мы знаем, что это не
так! При компиляции данного примера компилятор даже выдаст предупреждения:

(29,7): warning CS0183: The given expression is always of the provided
('NameCoUisions.ISerializable') type
(35,7): warning CS0183: The given expression is always of the provided
('NameCollisions.IDataStore') type
Проблема в том, что в классе реализованы или сериализованная версия
метода Bind, или версия, использующая БД (но не обе сразу). Однако
клиент получит положительный результат при проверке на реализацию
обеих версий этого интерфейса. При попытке задействовать
интерфейс, который на самом деле не реализован, результат непредсказуем.

Чтобы решить эту проблему, можно обратиться к явной квалификации
имени члена: уберем модификатор доступа и поставим перед именем
члена (в данном случае перед SaveData) имя интерфейса:
using System;
namespace NameCollisions
{
interface ISerializable
{
void SaveData();
interface IDataStore
i
void SaveData();
class Test: ISerializable, IDataStore
void ISerializable.SaveData()
Console.WriteLine("Test.ISerializable.SaveData() вызван")
void IDataStore.SaveData()
Console.WriteLine f"Test.IDataStore.SaveData(} вызван");
}
i
class NameCollisionsApp
public static void MainO
186 Раздел I I . Фундаментальные понятия
Test test = new Test();
if (test is ISerializable)
//Console.WriteLine("Вызов метода Test.SaveData()");
Console.WriteLine("Интерфейс ISerializable реализуется'
( (ISerializable)test) .SaveDara();
!
Console.WriteLine() ;
if (test is IDataStore)
//Console.WriteLine("Вызов метода Test.SaveData()");
Console.WriteLine("Интерфейс IDataStore реализуется");
( (IDataStore)test).SaveData() ;
Теперь можно сказать однозначно, какой метод будет вызван. Оба
метода реализованы с полностью квалифицированными именами, а приложение
выдает именно тот результат, которого вы ожидаете:
Интерфейс ISerializable реализуется
Test.ISerializable. SaveDataQ вызван
Интерфейс IDataStore реализуется
Test.IDataStore.SaveDataO вызван
РОЛЬ ИНТЕРФЕЙСОВ В НАСЛЕДОВАНИИ
С интерфейсами и наследованием связаны две распространенные проблемы.
Первая, проиллюстрированная приведенным ниже кодом, связана
с созданием производного класса, содержащего метод, чье имя
идентично имени метода интерфейса, который должен быть реализован
классом:
using System;
public interface IDataBound
{
void Serialize!);
public class Control
Интерфейсы 187
public void Serialize!)
{
Console.WriteLine("Метод Control.Serialize вызван"];
)
}
public class EditBox: Control, IDataBound
class InterfacelnhApp
I

public static void Main()
f
EditBox edit = new EditBox!);
edit.Serialize();
Как вы знаете, чтобы реализовать интерфейс, нужно предоставить определение
каждого члена в определении интерфейса. Однако в предыдущем
примере мы этого не сделали, а код все равно компилируется! Причина в
том, что компилятор С# ищет метод Serialize, реализованный в классе EditBox,
и находит его. Однако компилятор неверно определяет, что это реализованный
метод. Метод Serialize, найденный компилятором, унаследован от
класса Control методом, но не является настоящей реализацией метода
IDataBound.Serialize. Поэтому, хоть он и компилируется, код не будет функционировать,
как задумано, в чем мы убедимся позже.

Теперь внесем дополнительную интригу. Следующий код сначала
проверяет оператором as, реализован ли интерфейс, затем пытается вызвать
реализованный метод Serialize. Этот код компилируется и работает.
Однако, как мы знаем, в классе EditBox метод Serialize на самом деле не
реализован из-за наследования IDataBound. В EditBox уже есть метод Serialize
(унаследованный) от класса Control. Это значит, что клиент, по всей вероятности,
не получит ожидаемых результатов.
using System;
public interface IDataBound
{
void Serialize();
public class Control
{
public void Serializef)
188 Раздел II. Фундаментальные понятия
Console.WriteLine("Метод Control.Serialize вызван");
public class EditBox: Control, IDataBound
class InterfacelnhApp
{
public static void Mainf)
i
EditBox edit = new EditBox();
IDataBound bound = edit as IDataBound;
if(bound != null)
{
Console.WriteLine("Интерфейс IDataBound поддерживается");
bound.Serialize(];
else
Console.WriteLine("Интерфейс IDataBound не поддерживается
Возникновение другой потенциальной проблемы ожидается, когда у
производного класса есть метод с тем же именем, что и у реализации
метода интерфейса из базового класса. Давайте рассмотрим этот код:
using System;
public interface ITest
void Foo();
class Base: ITest
public void Foo()
Console.WriteLine(
"Реализация метода Foo интерфейса ITest в базовом классе");
I

class Derived: Base
Интерфейсы 189
public new void FooО
Console.WriteLine("Derived.Foo");
class InterfacelnhApp
public static void Mainf)
Derived der = new Derived!);
der .Foo () ;
ITest test = (ITest)der;
test.Foo ();
В результате выполнения этот код выводит информацию:
Derived. Foo
Реализация метода Foo интерфейса ITest в базовом классе
В этой ситуации в классе Base реализован интерфейс ITest и его метод
Foo. Однако производный от Base класс Derived реализует для этого класса
новый метод Foo. Какой из методов Foo будет вызван? Это зависит от
имеющейся у вас ссылки. Если есть ссылка на объект Derived, вызывается
его метод Foo. Это так, даже несмотря на то, что у объекта der есть
унаследованная реализация ITest.Foo. В период выполнения будет исполнен
Derived.Foo, так как ключевым словом new задана подмена унаследованного
метода.
Однако когда вы явно выполняете приведение объекта der к интерфейсу
ITest, компилятор разрешает реализацию интерфейса. У класса Derived есть
метод с тем же именем, но это не тот метод, что ищет компилятор. Когда
вы приводите объект к интерфейсу, компилятор проходит по дереву наследования,
пока не найдет класс, содержащий в своем базовом списке интерфейс.
Именно поэтому в результате выполнения последних двух строк кода
метода Main вызывается метод Foo, реализованный в ITest.
Хочется надеяться, что некоторые из этих потенциальных ловушек,
включая конфликты имен и наследование интерфейсов, убедили вас следовать
рекомендации: всегда приводить объекты к интерфейсу, член
которого вы пытаетесь использовать.
КОМБИНИРОВАНИЕ ИНТЕРФЕЙСОВ
Еще одна мощная функция С# - возможность комбинирования двух
или более интерфейсов, в результате чего класс должен реализовать только
результат комбинирования. Допустим, вы хотите создать новый класс
190 Раздел II. Фундаментальные понятия
TreeView, реализующий два интерфейса: IDragDrop и ISerializable. Поскольку
резонно предполагать, что и другим элементам управления, таким как
ListView и ListBox, понадобится скомбинировать эти функции, вам, возможно,
захочется скомбинировать интерфейсы IDragDrop и ISerializable в единое
целое:
using System;
public class Control
public interface IDrugDrop
{
void Drag();
void Drop() ;
public interface ISerializable
void Serialize ();
public interface ICombo:IDrugDrop, ISerializable
//этот интерфейс объединяет в себе
//два других интерфейса
}
public class MyTreeView: Control, ICombo
public void DragO
Console.WriteLine("Вызов метода MyTreeView.Drag");
public void Drop (|
Console.WriceLine("Вызов метода MyTreeView.Drop");
public void Serialized
Console.WriteLine("Вызов метода MyTreeView.Serialize")
class CombiningApp
public static void Main()
Интерфейсы 191
MyTreeView tree = new MyTreeView ();
tree.Drag();
tree.Drop();
tree.Serialize();
Комбинируя интерфейсы, вы не только упростите возможность использование
связанных интерфейсов, но при необходимости сможете добавлять
к новому "композитному" интерфейсу дополнительные методы.

16. ДЕЛЕГАТЫ И ОБРАБОТЧИКИ СОБЫТИИ
Одно из полезных нововведений в С# - делегаты (delegates). Их назначение,
по сути, совпадает с указателями функций в C++, но делегаты
являются управляемыми объектами и привязаны к типам. Это значит,
что исполняющая среда (runtime) гарантирует, что делегат указывает на
допустимый объект, а это в свою очередь означает получение всех достоинств
указателей функций без связанных с этим опасностей, таких как
применение недопустимых адресов или разрушение памяти других объектов.
В этой главе мы рассмотрим делегаты в сравнении с интерфейсами,
их синтаксис и проблемы применения. Мы также увидим несколько примеров
использования делегатов с функциями обратного вызова и асинхронными
обработчиками событий.
Из главы 15 вы узнали, как определяются и реализуются интерфейсы,
а также то, что с концептуальной точки зрения интерфейсы - это связки
между двумя различными частями кода. Но при этом интерфейсы во
многом напоминают классы, так как объявляются в период компиляции
и могут включать методы, свойства, индексаторы и события. Что касается
делегата, то он ссылается на единственный метод и определяется в
период выполнения. В С# две основных области применения делегатов:
методы обратного вызова и обработчики событий.
МЕТОДЫ ОБРАТНОГО ВЫЗОВА
Методы обратного вызова повсеместно используются в Windows для
передачи указателя функции другой функции, чтобы последняя могла
вызвать первую (через переданный ей указатель). Так, функция Win32
API Enum Windows перечисляет все окна верхнего уровня на экране и для
каждого окна вызывает переданную ей функцию. Обратные вызовы применяют
по-разному, но наиболее распространены два случая.
Асинхронная обработка. Методы обратного вызова используют при
асинхронной обработке, когда вызванному коду требуется значительное
время для обработки запроса. Обычно сценарий таков. Клиентский код
вызывает метод, передавая ему метод обратного вызова. Вызванный
метод начинает работу в своем потоке и сразу возвращает управление.
Запущенный поток затем выполняет основную работу, при необходимости
обращаясь к функции обратного вызова. Очевидное достоинство
такого подхода в том, что клиент продолжает работу, не блокируясь на
потенциально длительное время, которое требуется для синхронного
вызова.
Делегаты и обработчики событий 193
Введение дополнительного кода в код класса. Другой распространенный
способ применения методов обратного вызова имеет место, когда
класс позволяет клиенту указать метод для дополнительной нестандартной
обработки. Например, в классе Windows Listbox можно указать нисходящий
или восходящий порядок сортировки элементов. Кроме некоторых
других базовых возможностей для сортировки, этот класс на самом
деле не дает полной свободы действий и остается общим классом. Но
при этом Listbox позволяет указывать для сортировки функцию обратного
вызова. Таким образом, Listbox для сортировки вызывает функцию
обратного вызова, и ваш код может выполнять нужные нестандартные
действия.
Рассмотрим пример определения и применения делегата. Предположим,
у нас есть класс менеджера базы данных, который отслеживает
все активные соединения с БД и предоставляет метод перечисления
этих соединений. Если допустить, что менеджер БД находится на удаленной
машине, правильно будет сделать метод асинхронным, позволив
клиенту предоставлять метод обратного вызова. Заметьте: в реальном
приложении вам следовало бы задействовать многопоточность,
чтобы добиться подлинной асинхронности. Но для упрощения примера
и с учетом того, что мы не рассматривали многопоточность, не будем
ее прим

Список страниц

Закладка в соц.сетях

Купить

☏ Заказ рекламы: +380504468872

© Ассоциация электронных библиотек Украины

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