委派 (C++/CX)

delegate關鍵字是用來宣告參考型別,這是標準 C++ 中函式物件的Windows 執行階段對等專案。 委派宣告類似於函式簽章,會指定其包裝函式必須有的傳回類型和參數類型。 這是使用者定義的委派宣告:

public delegate void PrimeFoundHandler(int result);

委派最常與事件搭配使用。 事件有委派類型,與類別可以有介面類型十分相似。 委派代表事件處理常式必須滿足的合約。 以下是類型為先前定義的委派的事件類別成員:

event PrimeFoundHandler^ primeFoundEvent;

在宣告將公開給用戶端跨Windows 執行階段應用程式二進位介面的委派時,請使用 Windows::Foundation::TypedEventHandler < TSender,TResult > 。 此委派具有預先定義的 Proxy 與 Stub 二進位檔,使其能夠供 Javascript 用戶端使用。

使用委派

當您建立通用 Windows 平臺應用程式時,通常會使用委派作為Windows 執行階段類別公開的事件種類。 若要訂閱事件,請指定符合委派簽章的函式 (Lambda),建立事件的委派類型執行個體。 然後使用 += 運算子,將委派物件傳遞給類別的事件成員。 這就是所謂的訂閱事件。 當類別執行個體「引發」事件時,就會呼叫您的函式,以及任何其他已由您的物件或其他物件加入的處理常式。

提示

當您建立事件處理常式時,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)
    {/*...*/}

}

警告

一般來說,如果是事件處理常式,最好使用具名函式而不是 Lambda,除非您十分小心避免循環參考。 具名函式會依弱式參考擷取 "this" 指標,而 Lambda 則是依強式參考來擷取,並且會建立循環參考。 如需詳細資訊,請參閱 弱式參考和中斷迴圈

依照慣例,Windows 執行階段所定義的事件處理常式委派名稱的格式為 *EventHandler,例如 RoutedEventHandler、SizeChangedEventHandler 或 SuspendingEventHandler。 同樣依照慣例,事件處理常式委派會有兩個參數,並傳回 void。 在沒有類型參數的委派中,第一個參數是 Platform::Object^類型,它保存對傳送者的參考,傳送者是引發事件的物件。 您必須先轉換回原始類型,才能在事件處理常式方法中使用這個引數。 在具有類型參數的事件處理常式委派中,第一個類型參數指定傳送者的類型,而第二個參數則是 ref 類別的控制代碼,保存事件的相關資訊。 依照慣例,該類別名為 *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類型。 如需詳細資訊,請參閱事件

用戶端程式碼首先使用 ref new ,並提供與委派簽章相容的 Lambda 來建構委派執行個體,以及定義自訂行為。

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

接著呼叫成員函式,並傳遞委派。 假設 ciContactInfo^ 執行個體,而 textBlock 是 XAML TextBlock^

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

營造

您可以從下列任何物件建構委派:

  • Lambda

  • 靜態函式

  • 成員指標

  • 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" 指標的 Lambda,請務必使用 -= 運算子,明確地從事件中解除登錄,然後再結束 Lambda。 如需詳細資訊,請參閱事件

泛型委派

C++/CX 的泛型委派有類似於泛型類別宣告的限制。 它們不能宣告為公用。 您可以宣告私用或內部泛型委派,並從 C++ 取用它,但 .NET 或 JavaScript 用戶端無法取用它,因為它不會發出至 .winmd 中繼資料。 這個範例宣告僅供 C++ 使用的泛型委派:

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

下一個範例在類別定義內部宣告委派的特製化執行個體:

MyEventHandler<float>^ myDelegate;

委派和執行緒

委派就像函式物件,其中包含將在未來執行的程式碼。 如果建立並傳遞委派的程式碼,以及接受並執行委派的函式在相同執行緒上執行,事情就會很簡單。 如果該執行緒是 UI 執行緒,則委派可以直接操作使用者介面物件 (例如 XAML 控制項)。

如果用戶端應用程式載入線上程 Apartment 中執行的Windows 執行階段元件,並提供該元件的委派,則根據預設,委派會直接在 STA 執行緒上叫用。 大部分Windows 執行階段元件都可以在 STA 或 MTA 中執行。

如果執行委派的程式碼在不同的執行緒上執行 (例如,在 concurrency::task 物件的內容中),則您必須負責同步處理對共用資料的存取。 例如,如果您的委派包含對 Vector 的參考,而某個 XAML 控制項也參考相同 Vector,那麼您必須採取步驟,以避免當委派和 XAML 控制項同時嘗試存取 Vector 時,可能發生的死結或競爭情形。 您也必須注意不讓委派以傳址方式,擷取在叫用委派前可能就超出範圍的區域變數。

如果希望您建立的委派在其所建立的相同執行緒上回呼 (例如,假設您將委派傳遞給在 MTA Apartment 中執行的元件),而且希望在與建立者相同的執行緒上叫用,那麼請使用採用第二個 CallbackContext 參數的委派建構函式多載。 請只在具有已登錄 Proxy/Stub 的委派上使用此多載,不是每個以 Windows.winmd 定義的委派都有登錄。

如果您熟悉 .NET 的事件處理常式,就會知道建議的作法是在引發之前建立事件的本機複本。 這可避免在叫用事件之前可能移除事件處理常式的競爭情形。 在 C++/CX 中不需要這麼做,因為當新增或移除事件處理常式時,就會建立新的處理常式清單。 由於在叫用事件之前 C++ 物件會遞增事件處理常式清單上的參考計數,因此所有處理常式保證都是有效。 不過,這也表示,如果移除耗用端執行緒的事件處理常式,當發行物件在其現在已過時的清單複本上操作時,該處理常式可能仍然會被叫用。 直到下一次引發事件時,發行物件才會得到更新清單。

另請參閱

類型系統
C++/CX 語言參考
命名空間參考