Делегаты (C++/CX)

delegate Ключевое слово используется для объявления ссылочного типа, который является среда выполнения Windows эквивалентом объекта функции в стандартном C++. Объявление делегата похоже на сигнатуру функции; оно определяет тип возвращаемого значения и типы параметров, которые должна иметь заключенная в оболочку функция. Ниже показано пользователем объявление делегата:

public delegate void PrimeFoundHandler(int result);

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

event PrimeFoundHandler^ primeFoundEvent;

При объявлении делегатов, которые будут предоставляться клиентам в двоичном интерфейсе приложения среда выполнения Windows, используйте Windows::Foundation::TypedEventHandler<TSender, TResult>. Этот делегат имеет предопределенные двоичные прокси и заглушки, которые позволяют его использовать клиентами Javascript.

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

При создании приложения универсальная платформа Windows часто вы работаете с делегатом в качестве типа события, которое предоставляет класс среда выполнения Windows. Чтобы подписаться на событие, создайте экземпляр типа делегата, указав функцию (или лямбда-выражение), соответствующее сигнатуре делегата. Затем воспользуйтесь оператором += , чтобы передать объект делегата члену события в классе. Это называется "подписаться на событие". Когда экземпляр класса инициирует событие, вызывается ваша функция, а также другие обработчики, которые были добавлены данным объектом или другими объектами.

Совет

При создании обработчика событий Visual Studio выполняет многие действия автоматически. Например, при определении обработчика событий в разметке XAML появляется подсказка. Если щелкнуть подсказку, Visual Studio автоматически создаст метод обработчика событий и свяжет его с событием в классе публикации.

В следующем примере демонстрируется использование основного подхода. Windows::Foundation::TypedEventHandler — это тип делегата. Функция обработчика создана с помощью именованной функции.

В файле app.h:

[Windows::Foundation::Metadata::WebHostHiddenAttribute]
ref class App sealed
{        
    void InitializeSensor();
    void SensorReadingEventHandler(Windows::Devices::Sensors::LightSensor^ sender, 
        Windows::Devices::Sensors::LightSensorReadingChangedEventArgs^ args);

    float m_oldReading;
    Windows::Devices::Sensors::LightSensor^ m_sensor;

};

В файле app.cpp:

void App::InitializeSensor()
{
    // using namespace Windows::Devices::Sensors;
    // using namespace Windows::Foundation;
    m_sensor = LightSensor::GetDefault();

    // Create the event handler delegate and add 
    // it  to the object's  event handler list.
    m_sensor->ReadingChanged += ref new  TypedEventHandler<LightSensor^, 
        LightSensorReadingChangedEventArgs^>( this, 
        &App::SensorReadingEventHandler);

}

void App::SensorReadingEventHandler(LightSensor^ sender, 
                                    LightSensorReadingChangedEventArgs^ args)
{    
    LightSensorReading^ reading = args->Reading;
    if (reading->IlluminanceInLux > m_oldReading)
    {/*...*/}

}

Предупреждение

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

По соглашению имена делегатов обработчика событий, определенные среда выполнения Windows имеют форму *EventHandler, например RoutedEventHandler, SizeChangedEventHandler или SuspendingEventHandler. Также общепринято, что делегаты обработчиков событий имеют два параметра и возвращают значение void. В делегате, у которого нет параметров-типов, первый параметр принадлежит к типу Platform::Object^; он хранит ссылку на объект-отправитель, инициировавший событие. Необходимо выполнить приведение обратно в исходный тип, прежде чем использовать аргумент в методе обработчика событий. В делегате обработчика событий, имеющем параметры-типы, первый параметр-тип указывает тип отправителя, а второй параметр является дескриптором класса ссылки, который содержит сведения о событии. По соглашению этот класс называется *EventArgs. Например, делегат RoutedEventHandler имеет второй параметр типа RoutedEventArgs^, и DragEventHander имеет второй параметр типа DragEventArgs^.

В соответствии с общепринятой практикой делегаты, являющиеся оболочками для кода, который выполняется при завершении асинхронной операции, имеют имя *CompletedHandler. Эти делегаты определяются как свойства класса, а не как события. Поэтому оператор += не используется, чтобы подписаться на них; вместо этого объект делегата просто присваивается свойству.

Совет

C++ IntelliSense не показывает полную сигнатуру делегата, а потому не помогает определить конкретный тип параметра EventArgs. Чтобы определить его тип, можно открыть Обозреватель объектов и просмотреть сведения о методе Invoke делегата.

Создание пользовательских делегатов

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

Объявление

Объявление делегата похоже на объявление функции, за исключением того что делегат — это тип. Обычно делегат объявляется в области пространства имен, хотя также можно вложить объявление делегата внутрь объявления класса. Следующий делегат инкапсулирует любую функцию, принимающую аргумент ContactInfo^ и возвращающую значение Platform::String^.

public delegate Platform::String^ CustomStringDelegate(ContactInfo^ ci);

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

public ref class ContactInfo sealed
{        
public:
    ContactInfo(){}
    ContactInfo(Platform::String^ saluation, Platform::String^ last, Platform::String^ first, Platform::String^ address1);
    property Platform::String^ Salutation;
    property Platform::String^ LastName;
    property Platform::String^ FirstName;
    property Platform::String^ Address1;
    //...other properties

    Platform::String^ ToCustomString(CustomStringDelegate^ func)
    {
        return func(this);
    }       
};

Примечание.

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

Объявление события всегда имеет тип делегата. В этом примере показана типичная подпись типа делегата в среда выполнения Windows:

public delegate void RoutedEventHandler(
    Platform::Object^ sender, 
    Windows::UI::Xaml::RoutedEventArgs^ e
    );

Событие Click в классе Windows:: UI::Xaml::Controls::Primitives::ButtonBase принадлежит к типу RoutedEventHandler. Для получения дополнительной информации см. Events.

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

CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
    return c->FirstName + " " + c->LastName;
});

Затем он вызывает функцию-член и передает делегат. Предположим, что ci является экземпляром класса ContactInfo^ и textBlock является конструкцией TextBlock^XAML-кода.

textBlock->Text = ci->ToCustomString( func );

В следующем примере клиентское приложение передает пользовательский делегат общедоступному методу в компоненте среда выполнения Windows, который выполняет делегат для каждого элемента в:Vector

//Client app
obj = ref new DelegatesEvents::Class1();

CustomStringDelegate^ myDel = ref new CustomStringDelegate([] (ContactInfo^ c)
{
    return c->Salutation + " " + c->LastName;
});
IVector<String^>^ mycontacts = obj->GetCustomContactStrings(myDel);
std::for_each(begin(mycontacts), end(mycontacts), [this] (String^ s)
{
    this->ContactString->Text += s + " ";
});

 

// Public method in WinRT component.
IVector<String^>^ Class1::GetCustomContactStrings(CustomStringDelegate^ del)
{
    namespace WFC = Windows::Foundation::Collections;

    Vector<String^>^ contacts = ref new Vector<String^>();
    VectorIterator<ContactInfo^> i = WFC::begin(m_contacts);
    std::for_each( i ,WFC::end(m_contacts), [contacts, del](ContactInfo^ ci)
    {
        contacts->Append(del(ci));
    });

    return contacts;
}

Строительство

Делегат можно создать из любого из следующих объектов:

  • лямбда-оператор

  • статическая функция;

  • указатель на член;

  • std::function.

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


ContactInfo^ ci = ref new ContactInfo("Mr.", "Michael", "Jurek", "1234 Compiler Way");

// Lambda. (Avoid capturing "this" or class members.)
CustomStringDelegate^ func = ref new CustomStringDelegate([] (ContactInfo^ c)
{
    return c->Salutation + " " + c->FirstName + " " + c->LastName;
});

// Static function.
// static Platform::String^ GetFirstAndLast(ContactInfo^ info);   
CustomStringDelegate^ func2 = ref new CustomStringDelegate(Class1::GetFirstAndLast);


// Pointer to member.
// Platform::String^ GetSalutationAndLast(ContactInfo^ info)
CustomStringDelegate^ func3 = ref new CustomStringDelegate(this, &DelegatesEvents::Class1::GetSalutationAndLast);

// std::function
std::function<String^ (ContactInfo^)> f = Class1::GetFirstAndLast;
CustomStringDelegate^ func4 = ref new CustomStringDelegate(f);


// Consume the delegates. Output depends on the 
// implementation of the functions you provide.
textBlock->Text  = func(ci); 
textBlock2->Text = func2(ci);
textBlock3->Text = func3(ci);
textBlock4->Text = func4(ci);

Предупреждение

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

Универсальные делегаты

Универсальные делегаты в C ++/CX имеют ограничения, аналогичные объявлениям универсальных классов. Они не могут объявляться как открытые. Вы можете объявить частный или внутренний универсальный делегат и использовать его из C++, но клиенты .NET или JavaScript не могут использовать его, так как он не создается в метаданных winmd. В этом примере объявляется универсальный делегат, который можно использовать только в коде C++:

generic <typename T>
delegate void  MyEventHandler(T p1, T p2);

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

MyEventHandler<float>^ myDelegate;

Делегаты и потоки

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

Если клиентское приложение загружает компонент среда выполнения Windows, работающий в потоке, и предоставляет делегат этого компонента, то по умолчанию делегат вызывается непосредственно в потоке STA. Большинство среда выполнения Windows компонентов могут выполняться в STA или MTA.

Если код, который выполняет делегат, выполняется в другом потоке (например, в контексте объекта concurrency::task), вам необходимо синхронизировать доступ к общим данным. Например, если делегат содержит ссылку на объект Vector и элемент управления XAML содержит ссылку на тот же самый объект Vector, необходимо принять меры, чтобы избежать взаимоблокировки и состояния гонки, которые могут возникнуть, если делегат и элемент управления XAML попытаются обратиться к объекту Vector одновременно. Необходимо также обеспечить, чтобы делегат не пытался захватить с помощью ссылок локальные переменные, которые могут оказаться вне области до того, как произойдет вызов делегата.

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

Если вы знакомы с обработчиками событий в .NET, вам известно, что перед возникновением события рекомендуется создать его локальную копию. Это позволяет избежать состояния гонки, при котором обработчик события может быть удален непосредственно перед вызовом события. Это не нужно делать в C++/CX, так как при добавлении или удалении обработчиков событий создается новый список обработчиков. Поскольку объект C++ увеличивает счетчик ссылок в списке обработчиков до вызова события, это гарантирует, что все обработчики будут действительными. Однако это также означает, что если удалить обработчик событий в потоке-потребителе, этот обработчик все равно может быть вызван, если публикующий объект все еще работает со своей копией списка, которая к этому моменту устарела. Публикующий объект не получит обновленный список до следующего вызова события.

См. также

Система типов
Справочник по языку C++/CX
Справочник по пространствам имен