Delegados (C++/CX)

La palabra clave delegate se usa para declarar un tipo de referencia que es el equivalente de Windows Runtime a un objeto de función en C++ estándar. Una declaración de delegado es similar a una signatura de función; especifica el tipo de valor devuelto y los tipos de parámetros que debe tener la función incluida. Esta es una declaración de delegado definida por el usuario:

public delegate void PrimeFoundHandler(int result);

Los delegados suelen usarse junto con eventos. Un evento tiene un tipo de delegado, de la misma manera que una clase puede tener un tipo de interfaz. El delegado representa un contrato que deben cumplir los controladores de eventos. Este es un miembro de clase de eventos cuyo tipo es el delegado definido anteriormente:

event PrimeFoundHandler^ primeFoundEvent;

Al declarar delegados que se van a exponer a clientes en la interfaz binaria de aplicación de Windows Runtime, use Windows::Foundation::TypedEventHandler<TSender, TResult>. Este delegado tiene binarios de código auxiliar y proxy predefinidos que permiten su uso en clientes de JavaScript.

Usar delegados

Cuando se crea una aplicación de Plataforma universal de Windows, se suele trabajar con un delegado como el tipo de un evento que expone una clase de Windows Runtime. Para suscribirte a un evento, crea una instancia de su tipo de delegado especificando una función, o expresión lambda, que coincida con la firma de delegado. Después, usa el operador += para pasar el objeto de delegado al miembro de evento de la clase. Esto se denomina suscribirse al evento. Cuando la instancia de clase "desencadena" el evento, se llama a tu función junto con cualquier otro controlador que haya sido agregado por tu objeto o por otros objetos.

Sugerencia

Visual Studio realiza mucho trabajo automáticamente cuando creas un controlador de eventos. Por ejemplo, si especificas un controlador de eventos en marcado XAML, aparece una información sobre herramientas. Si eliges la información sobre herramientas, Visual Studio crea automáticamente el método de controlador de eventos y lo asocia al evento en la clase de publicación.

En el ejemplo siguiente se muestra el patrón básico. Windows::Foundation::TypedEventHandler es el tipo de delegado. La función de controlador se crea utilizando una función con nombre.

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

};

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

}

Advertencia

En general, para un controlador de eventos es mejor usar una función con nombre en lugar de una expresión lambda, a menos que tengas mucho cuidado para evitar referencias circulares. Una función con nombre captura el puntero "this" mediante referencia débil, mientras que una expresión lambda lo captura mediante referencia segura y crea una referencia circular. Para obtener más información, vea Referencias parciales y ruptura de ciclos.

Por convención, los nombres de delegado de controlador de eventos definidos por Windows Runtime tienen el formato *EventHandler, por ejemplo, RoutedEventHandler, SizeChangedEventHandler o SuspendingEventHandler. También por convención, los delegados de controladores de eventos tienen dos parámetros y devuelven void. En un delegado que no tiene parámetros de tipo, el primer parámetro es de tipo Platform::Object^; contiene una referencia al remitente, que es el objeto que desencadenó el evento. Tienes que volver a convertir al tipo original antes de usar el argumento en el método de controlador de eventos. En un delegado de controlador de eventos que tiene parámetros de tipo, el primer parámetro de tipo especifica el tipo del remitente y el segundo parámetro es un identificador de una clase ref que contiene información sobre el evento. Por convención, esa clase se denomina *EventArgs. Por ejemplo, un delegado RoutedEventHandler tiene un segundo parámetro de tipo RoutedEventArgs^ y DragEventHander tiene un segundo parámetro de tipo DragEventArgs^.

Por convención, los delegados que contienen el código que se ejecuta cuando se completa una operación asincrónica se denominan *CompletedHandler. Estos delegados se definen como propiedades en la clase, no como eventos. Por consiguiente, no se utiliza el operador += para suscribirse a ellas; solo tienes que asignar un objeto de delegado a la propiedad.

Sugerencia

IntelliSense de C++ no muestra la firma completa de delegado; por tanto, no ayuda a determinar el tipo específico del parámetro EventArgs. Para encontrar el tipo, puedes ir al Examinador de objetos y ver el método Invoke para el delegado.

Crear delegados personalizados

Puede definir sus propios delegados para definir controladores de eventos o para permitir que los consumidores pasen funcionalidad personalizada al componente de Windows Runtime. Como cualquier otro tipo de Windows Runtime, un delegado público no se puede declarar como genérico.

Declaración

La declaración de un delegado es similar a una declaración de función salvo que el delegado es un tipo. Normalmente, declaras un delegado en el ámbito del espacio de nombres, aunque también puedes anidar una declaración de delegado en una declaración de clase. El delegado siguiente encapsula cualquier función que toma ContactInfo^ como entrada y devuelve Platform::String^.

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

Después de declarar un tipo de delegado, puedes declarar miembros de clase de ese tipo o métodos que toman objetos de ese tipo como parámetros. Un método o una función también puede devolver un tipo de delegado. En el ejemplo siguiente, el método ToCustomString toma el delegado como parámetro de entrada. El método permite que el código de cliente proporcione una función personalizada que construye una cadena a partir de algunas o de todas las propiedades públicas de un 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);
    }       
};

Nota:

Use el símbolo "^" cuando haga referencia al tipo de delegado, como haría con cualquier tipo de referencia de Windows Runtime.

Una declaración de evento siempre tiene un tipo de delegado. En este ejemplo se muestra una firma típica de tipo de delegado en Windows Runtime:

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

El evento Click de la clase Windows:: UI::Xaml::Controls::Primitives::ButtonBase es de tipo RoutedEventHandler. Para más información, vea Eventos.

El código de cliente primero crea la instancia del delegado mediante ref new y proporcionando una expresión lambda que es compatible con la firma de delegado y define el comportamiento personalizado.

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

Después llama a la función miembro y pasa el delegado. Imagina que ci es una instancia de ContactInfo^ y textBlock es un TextBlock^XAML.

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

En el ejemplo siguiente, una aplicación cliente pasa un delegado personalizado a un método público en un componente de Windows Runtime que ejecuta el delegado en cada elemento de 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;
}

Construcción

Puedes construir un delegado a partir de cualquiera de estos objetos:

  • lambda

  • función estática

  • puntero a miembro

  • std::function

En el ejemplo siguiente se muestra cómo construir un delegado a partir de cada uno de estos objetos. Usa el delegado de la misma forma independientemente del tipo de objeto que se use para crearlo.


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

Advertencia

Si empleas una expresión lambda que captura el puntero “this”, asegúrate de usar el operador -= para anular explícitamente el registro del evento antes de salir de la expresión lambda. Para más información, vea Eventos.

Delegados genéricos

Los delegados genéricos en C++/CX tienen restricciones similares a las declaraciones de clases genéricas. No pueden declararse como públicos. Puede declarar un delegado genérico privado o interno y consumirlo desde C++, pero los clientes de .NET o JavaScript no pueden consumirlo porque no se emite en los metadatos .winmd. En este ejemplo se declara un delegado genérico que solo se puede utilizar en C++:

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

En el ejemplo siguiente se declara una instancia especializada del delegado dentro de una definición de clase:

MyEventHandler<float>^ myDelegate;

Delegados y subprocesos

Un delegado, como un objeto de función, contiene código que se ejecutará en algún momento en el futuro. Si el código que crea y pasa el delegado, y la función que acepta y ejecuta el delegado, se están ejecutando en el mismo subproceso, las cosas son relativamente simples. Si ese subproceso es el subproceso de la interfaz de usuario, el delegado puede manipular directamente objetos de la interfaz de usuario como controles XAML.

Si una aplicación cliente carga un componente de Windows Runtime que se ejecuta en un contenedor uniproceso, y proporciona un delegado a ese componente, el delegado se invoca directamente en el subproceso STA de manera predeterminada. La mayoría de los componentes de Windows Runtime pueden ejecutarse en STA o MTA.

Si el código que ejecuta el delegado se está ejecutando en otro subproceso (por ejemplo, en el contexto de un objeto concurrency::task), tú eres responsable de sincronizar el acceso a los datos compartidos. Por ejemplo, si tu delegado contiene una referencia a un Vector y un control XAML tiene una referencia a ese mismo Vector, debes realizar los pasos necesarios para evitar interbloqueos o condiciones de carrera que pueden producirse cuando tanto el delegado como el control XAML intentan obtener acceso al Vector al mismo tiempo. También debes tener cuidado para que el delegado no intente capturar por referencia las variables locales que podrían salir del ámbito antes de que se invoque el delegado.

Si deseas que se vuelva a llamar al delegado que has creado en el mismo subproceso en el que lo creaste (por ejemplo, si lo pasas a un componente que se ejecuta en un contenedor MTA) y deseas que se invoque en el mismo subproceso que el creador, usa la sobrecarga del constructor delegado que toma un segundo parámetro CallbackContext . Usa solo esta sobrecarga en delegados que tengan un proxy o código auxiliar registrado; no todos los delegados que se definen en Windows.winmd están registrados.

Si estás familiarizado con los controladores de eventos de .NET, ya sabrás que la práctica recomendada consiste en crear una copia local de un evento antes de desencadenarlo. Esto evita las condiciones de carrera en las que se podría quitar un controlador de eventos justo antes de invocar al evento. No es necesario hacerlo en C++/CX porque cuando se agregan o quitan controladores de eventos, se crea una nueva lista de controladores. Dado que un objeto C++ incrementa el recuento de referencias en la lista de controladores antes de invocar un evento, se garantiza que todos los controladores serán válidos. Sin embargo, esto también significa que si quitas un controlador de eventos en el subproceso utilizado, se podría seguir invocando a ese controlador si el objeto de publicación sigue funcionando en su copia de la lista, que ahora está obsoleta. El objeto de publicación no obtendrá la lista actualizada hasta la próxima vez que desencadene el evento.

Consulte también

Sistema de tipos
Referencia del lenguaje C++/CX
Referencia de espacios de nombres