Delegados (C++/CX)

A palavra-chave delegate é usada para declarar um tipo de referência que é o equivalente do Windows Runtime de um objeto de função em C++ padrão. Uma declaração delegate semelhante a uma assinatura de função especifica o tipo de retorno e os tipos de parâmetro que sua função envolvida deve ter. Esta é uma declaração delegate definida pelo usuário:

public delegate void PrimeFoundHandler(int result);

Delegados são mais comumente usados em conjunto com eventos. Um evento tem um tipo delegate da mesma maneira que uma classe que pode ter um tipo de interface. O delegate representa um contrato que é cumprido pelos manipuladores de eventos. Aqui está um membro da classe de evento cujo tipo é o delegado definido anteriormente:

event PrimeFoundHandler^ primeFoundEvent;

Ao declarar representantes que ficarão expostos aos clientes ao longo da interface binária do aplicativo de Windows Runtime, use Windows::Foundation::TypedEventHandle<TSender, TResult>. Este representante possui proxy predefinido e binários stub que permitem seu consumo por clientes JavaScript.

Consumindo delegados

Ao cria um aplicativo Plataforma Universal do Windows, geralmente trabalha com um delegado como o tipo de um evento exposto por uma classe do Windows Runtime. Para assinar um evento, crie uma instância do tipo do seu representante especificando uma função (ou lambda) que corresponda à assinatura do representante. Use o operador += para passar o objeto do representante ao membro de evento na classe. Isso é conhecido como assinar o evento. Quando a instância da classe "dispara" o evento, sua função é chamada, juntamente com qualquer outro manipulador que tenha sido adicionado pelo seu objeto ou outros objetos.

Dica

O Visual Studio faz grande parte do trabalho para você na criação de um manipulador de eventos. Por exemplo, se você especificar um manipulador de eventos na marcação XAML, uma dica de ferramenta será exibida. Se você escolher a dica de ferramenta, o Visual Studio criará o método de manipulador de eventos automaticamente e o associará ao evento na classe de publicação.

O exemplo a seguir mostra o padrão básico. Windows::Foundation::TypedEventHandler é o tipo delegado. A função do manipulador é criada usando uma função nomeada.

Em 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;

};

Em 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)
    {/*...*/}

}

Aviso

Em geral, para um manipulador de eventos, é melhor usar uma função nomeada em vez de uma lambda, a menos que você seja muito cuidadoso a fim de evitar referências circulares. Uma função nomeada captura o ponteiro "this" por referência fraca, mas uma lambda captura-o por referência forte e cria uma referência circular. Para obter mais informações, consulte Referências fracas e quebra de ciclos.

Por convenção, os nomes de representantes do manipulador de eventos definidos pelo Windows Runtime têm o formato *EventHandler, por exemplo, RoutedEventHandler, SizeChangedEventHandler ou SuspendingEventHandler. Também por convenção, os delegados do manipulador de eventos têm dois parâmetros e retornar void. Em um representante que não tenha parâmetros de tipo, o primeiro parâmetro é do tipo Platform::Object Class^; ele contém uma referência para o remetente, que é o objeto que disparou o evento. Você tem que converter novamente no tipo original antes de usar o argumento no método do manipulador de eventos. Em um representante do manipulador de eventos que tenha parâmetros de tipo, o primeiro parâmetro de tipo especifica o tipo de remetente e o segundo parâmetro é um identificador para uma classe ref que contém informações sobre o evento. Por convenção, essa classe é chamada *EventArgs. Por exemplo, um delegado RoutedEventHandler tem um segundo parâmetro do tipo RoutedEventArgs^ e DragEventHander tem um segundo parâmetro do tipo DragEventArgs^.

Por convenção, os representantes que encapsularam o código que é executado quando uma operação assíncrona é concluída são denominados *CompletedHandler. Esses representantes são definidos como propriedades na classe, e não como eventos. Desse modo, não use o operador += para assiná-los; você apenas atribui um objeto de representante à propriedade.

Dica

O C++ IntelliSense não mostra a assinatura completa do representante; portanto, isso não ajuda a determinar o tipo específico do parâmetro EventArgs. Para localizar o tipo, você pode ir para Pesquisador de Objetos e examinar o método Invoke do representante,

Criando delegados personalizados

Você pode definir seus próprios delegados, para definir manipuladores de eventos ou permitir que os clientes passem funcionalidade personalizada ao seu componente do Windows Runtime. Como qualquer outro tipo Windows Runtime, um delegado público não pode ser declarado como genérico.

Declaração

A declaração de um delegado é semelhante a uma declaração da função, exceto pelo fato de um delegado ser um tipo. Em geral, você declara um representante no escopo do namespace, embora também possa aninhar uma declaração de representante em uma declaração de classe. O representante a seguir encapsula qualquer função que tenha ContactInfo^ como entrada e retorna Platform::String^.

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

Depois que você declarar um tipo de representante, poderá declarar membros de classe de tipo ou de métodos que usam objetos desse tipo como parâmetros. Um método ou uma função também podem retornar um tipo delegado. No exemplo a seguir, o método ToCustomString usa o delegado como um parâmetro de entrada. O método permite que o código de cliente forneça uma função personalizada que constrói uma cadeia de caracteres de algumas ou todas as propriedades públicas de um objeto 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);
    }       
};

Observação

Use o símbolo de "^" ao se referir ao tipo delegado, exatamente como você faria com qualquer tipo de referência do Windows Runtime.

Uma declaração de evento sempre tem um tipo delegado. Este exemplo mostra uma assinatura de tipo delegado típica no Windows Runtime:

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

O evento Click na classe Windows:: UI::Xaml::Controls::Primitives::ButtonBase é do tipo RoutedEventHandler. Para obter mais informações, consulte Eventos.

O código de cliente primeiro constrói a instância delegada usando ref new e fornecendo um lambda compatível com a assinatura do delegado e define o comportamento personalizado.

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

Em seguida, chama a função de membro e passa o delegado. Vamos supor que ci seja uma instância de ContactInfo^ e textBlock seja um TextBlock^XAML.

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

No exemplo a seguir, um aplicativo cliente passa um representante personalizado a um método público em um componente do Windows Runtime que executa o representante em relação a cada item em 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;
}

Construção

Você pode construir um delegado de qualquer um destes objetos:

  • lambda

  • static function

  • pointer-to-member

  • std::function

O exemplo a seguir mostra como construir um delegado com base em cada um desses objetos. Você consome o delegado exatamente da mesma maneira, independentemente do tipo de objeto que é usado para construí-lo.


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);

Aviso

Se você usar uma lambda que captura o ponteiro "this", certifique-se de usar explicitamente o operador -= para cancelar explicitamente o registro do evento antes de sair da lambda. Para obter mais informações, consulte Eventos.

Delegados genéricos

Delegates genéricos em C++/CX têm restrições semelhantes às declarações de classes genéricas. Eles não podem ser declarados como públicos. Você pode declarar um delegado genérico privado ou interno e consumi-lo do C++, mas os clientes .NET ou JavaScript não podem consumi-lo porque ele não é emitido nos metadados .winmd. Este exemplo declara um delegate genérico que só pode ser consumido por C++:

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

O exemplo a seguir declara uma instância especializada do delegate dentro de uma definição de classe:

MyEventHandler<float>^ myDelegate;

Delegados e threads

Um delegado, assim como um objeto de função, contém o código que será executado a qualquer momento no futuro. Se o código que cria e passa o delegado e a função que aceita e executa o delegado estiver sendo executado no mesmo thread, as coisas serão relativamente simples. Se esse thread for o thread de IU, o delegado poderá manipular diretamente os objetos da interface de usuário, como controles XAML.

Se um aplicativo cliente carregar um componente do Windows Runtime executado em um threaded apartment e fornecer um representante a esse componente, por padrão, o representante será invocado diretamente no thread STA. A maioria dos componentes do Windows Runtime pode ser executada em STA ou MTA.

Se o código que executa o representante estiver sendo executado em um thread diferente, por exemplo, no contexto de um objeto concurrency::task, você será responsável pela sincronização do acesso aos dados compartilhados. Por exemplo, se o seu delegado contiver uma referência a um Vector, e um controle XAML tiver referência ao mesmo Vector, você deverá executar etapas para evitar deadlocks ou condições de corrida que podem ocorrer quando tanto o delegado quanto o controle XAML tentam acessar o Vector ao mesmo tempo. Você também deve tomar cuidado para que o delegado não tente capturar por referência variáveis locais que podem estar fora do escopo antes da invocação do delegado.

Se você quiser que o delegado criado seja chamado novamente no mesmo thread em que foi criado, por exemplo, se você passá-lo a um componente executado em um MTA apartment, e quiser que ele seja invocado no mesmo thread do criador, use a sobrecarga de construtor delegado que utiliza um segundo parâmetro CallbackContext . Somente use essa sobrecarga nos delegados que têm um proxy/stub registrado; nem todos os delegados que são definidos em Windows.winmd são registrados.

Se você estiver familiarizado com os manipuladores de eventos no .NET, sabe que a prática recomendada é fazer uma cópia local de um evento antes do acioná-lo. Isso evita as condições de corrida em que um manipulador de eventos pode ser removido imediatamente antes de o evento ser invocado. Não é necessário fazer isso em C++/CX porque quando os manipuladores de eventos são adicionados ou removidos, uma nova lista de manipuladores é criada. Como um objeto C++ incrementa a contagem de referência na lista de manipuladores antes de chamar um evento, é garantido que todos os manipuladores serão válidos. No entanto, isso também significa que, se você remover um manipulador de eventos do thread de consumo, esse manipulador ainda poderá ser invocado se o objeto de publicação ainda estiver funcionando em sua cópia da lista, que agora estará desatualizada. O objeto de publicação não obterá a lista atualizada até a próxima vez que ele acionar o evento.

Confira também

Sistema de tipos
Referência da linguagem C++/CX
Referência de namespaces