從 C# 移到 C++/WinRT
提示
如果您先前已閱讀過本主題,並且因為想了解特定工作而返回此處,您可以跳至<根據正在執行的工作來尋找內容>一節。
本主題詳細說明將 C# 專案中的原始程式碼移植到其在 C++/WinRT 中的對等項目時所涉及的技術詳細資料。
如需移植其中一個通用 Windows 平台 (UWP) 應用程式範例的案例研究,請參閱附屬主題:將剪貼簿範例從 C# 移植到 C++/WinRT。 您可以遵循逐步解說來獲得移植的方法和體驗,然後再自行移植範例。
如何準備,以及預期的事項
將剪貼簿範例從 C# 移植到 C++/WinRT 的案例研究會說明將專案移植到 C++/WinRT 時所需的各種軟體設計決策範例。 因此,您應先了解現有程式碼的運作方式,再準備進行移植。 如此一來,您就可以大致了解應用程式的功能,以及程式碼的結構,然後您所做的決定將會讓您向前邁進,並以正確的方向進行。
就預期的移植變更種類而言,您可以將其分為四個類別。
- 移植語言投影。 Windows 執行階段 (WinRT) 會「投影」到各種程式設計語言中。 每一個語言投影都會設計成所述程式設計語言慣用的型態。 針對 C#,某些 Windows 執行階段類型會投影為 .NET 類型。 例如,您會將 System.Collections.Generic.IReadOnlyList<T> 轉譯回 Windows.Foundation.Collections.IVectorView<T>。 此外,在 C# 中,某些 Windows 執行階段作業會投影為方便的 C# 語言功能。 例如,您會在 C# 使用
+=
運算子語法來註冊事件處理委派。 因此,您會把這類語言功能轉譯回要執行的基本作業 (在此範例中為事件註冊)。 - 移植語言語法。 這些變更有許多都是簡單的機械轉換,也就是將符號取代為另一個符號。 例如,將點 (
.
) 變更為雙冒號 (::
)。 - 移植語言程序。 這其中有一些是簡單的重複變更 (例如將
myObject.MyProperty
變更為myObject.MyProperty()
)。 而其他則是更深入的變更 (例如,將牽涉到使用 System.Text.StringBuilder 的程序移植到涉及使用 std::wostringstream 的程序)。 - C++/WinRT 特定的移植相關工作。 Windows 執行階段的特定詳細資料會透過 C# 在幕後進行隱含處理。 這些詳細資料會在 C++/WinRT 中明確執行。 例如,您可以使用
.idl
檔案來定義您的執行階段類別。
在接下來的工作型索引之後,本主題中的其餘章節會根據上述分類進行結構化。
根據正在執行的工作來尋找內容
Task | Content |
---|---|
撰寫 Windows 執行階段元件 (WRC) | 某些功能 (或某些 API 呼叫) 只能使用 C++ 來達成。 您可以將該功能納入 C++/WinRT WRC,然後從 C# 應用程式之類的項目中取用 WRC。 請參閱使用 C++/WinRT 的 Windows 執行階段元件和如果您正在 Windows 執行階段元件中撰寫執行階段類別。 |
移植非同步方法 | 建議您將 C++/WinRT 執行階段類別中非同步方法的第一行設定為 auto lifetime = get_strong(); (請參閱安全地存取類別成員協同程式中的 this 指標)。從 Task 移植,請參閱非同步動作。從 Task<T> 移植,請參閱非同步作業。從 async void 移植,請參閱射後不理 (Fire-and-Forget) 方法。 |
移植類別 | 首先,決定類別是否必須是執行階段類別,或是否可以是一般類別。 為協助您做決定,請參閱使用 C++/WinRT 撰寫 API 的最初部分。 接下來,請參閱下面三個資料列。 |
移植執行階段類別 | 在 C++ 應用程式外部共用功能的類別,或用於 XAML 資料繫結中的類別。 請參閱如果您正在 Windows 執行階段元件中撰寫執行階段類別,或是如果您正在撰寫要在 XAML UI 中參考的執行階段類別。 這些連結會更詳細地說明這一點,但必須在 IDL 中宣告執行階段類別。 如果您的專案已經包含 IDL 檔案 (例如 Project.idl ),則建議您在該檔案中宣告任何新的執行階段類別。 在 IDL 中,宣告將在您應用程式外部使用或將在 XAML 中使用的任何方法和資料成員。 更新 IDL 檔案之後,在您專案的 Generated Files 資料夾重建並查看產生的 stub 檔案 (.h 和 .cpp ) (在已選取專案節點的方案總管中,確定 [顯示所有檔案] 已切換為開啟)。 比較 stub 檔案與專案中已經存在的檔案,並視需要新增檔案或新增/更新函式簽章。 stub 檔案語法一律是正確的,因此我們建議您使用此檔案來將組建錯誤降至最低。 一旦專案中的 stub 與 stub 檔案中的項目相符,您就可以藉由移植 C# 程式碼來繼續並加以實作。 |
移植一般類別 | 請參閱如果您「不」撰寫執行階段類別。 |
撰寫 IDL | Microsoft 介面定義語言 3.0 的簡介 如果您正在撰寫要在 XAML UI 中參考的執行階段類別 取用 XAML 標記中的物件 定義 IDL 中的執行階段類別 |
移植集合 | 使用 C++/WinRT 的集合 讓資料來源可供 XAML 標記使用 關聯容器 向量成員存取 |
移植事件 | 事件處理常式委派為類別成員 撤銷事件處理常式委派 |
移植方法 | 從 C#:private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... } 至 C++/WinRT .h 檔案:fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&); 至 C++/WinRT .cpp 檔案:fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...} |
移植字串 | C++/WinRT 中的字串處理 ToString 字串建立 進行字串的 Box 處理和 Unbox 處理 |
類型轉換 (類型轉型) | C#: o.ToString() C++/WinRT: to_hstring(static_cast<int>(o)) 另請參閱 ToString。 C#: (Value)o C++/WinRT: unbox_value<Value>(o) unboxing 失敗時擲回。 另請參閱 Boxing 與 unboxing。 C#: o as Value? ?? fallback C++/WinRT: unbox_value_or<Value>(o, fallback) unboxing 失敗時傳回後援。 另請參閱 Boxing 與 unboxing。 C#: (Class)o C++/WinRT: o.as<Class>() 轉換失敗時擲回。 C#: o as Class C++/WinRT: o.try_as<Class>() 如果轉換失敗,則傳回 null。 |
涉及語言投射的變更
類別 | C# | C++/WinRT | 另請參閱 |
---|---|---|---|
非類型化物件 | object 或 System.Object |
Windows::Foundation::IInspectable | 移植 EnableClipboardContentChangedNotifications 方法 |
投影命名空間 | using System; |
using namespace Windows::Foundation; |
|
using System.Collections.Generic; |
using namespace Windows::Foundation::Collections; |
||
集合的大小 | collection.Count |
collection.Size() |
移植 BuildClipboardFormatsOutputString 方法 |
典型集合類型 | IList<T>,然後 [新增] 以新增元素。 | IVector<T>,然後 [附加] 以新增元素。 如果您使用 std::vector 任何位置,則 push_back 以新增元素。 | |
唯讀集合類型 | IReadOnlyList<T> | IVectorView<T> | 移植 BuildClipboardFormatsOutputString 方法 |
事件處理常式委派為類別成員 | myObject.EventName += Handler; |
token = myObject.EventName({ get_weak(), &Class::Handler }); |
移植 EnableClipboardContentChangedNotifications 方法 |
撤銷事件處理常式委派 | myObject.EventName -= Handler; |
myObject.EventName(token); |
移植 EnableClipboardContentChangedNotifications 方法 |
關聯容器 | IDictionary<K, V> | IMap<K, V> | |
向量成員存取 | x = v[i]; v[i] = x; |
x = v.GetAt(i); v.SetAt(i, x); |
註冊/撤銷事件處理常式
在 C++/WinRT 中,您有數個語法選項可供您註冊/撤銷事件處理常式委派,如藉由在 C++/WinRT 使用委派來處理事件中所述。 另請參閱移植 EnableClipboardContentChangedNotifications 方法.
例如,有時候事件收件者 (處理事件的物件) 即將終結時,您可以撤銷事件處理常式,讓事件來源 (引發事件的物件) 不會呼叫已終結的物件。 請參閱撤銷已註冊的委派。 在這種情況下,請為事件處理常式建立 event_token 成員變數。 如需範例,請參閱移植 EnableClipboardContentChangedNotifications 方法。
您也可以在 XAML 標記中註冊事件處理常式。
<Button x:Name="OpenButton" Click="OpenButton_Click" />
在 C# 中,OpenButton_Click 方法可以是私有的,而且 XAML 仍可將其連線到 OpenButton 所引發的 ButtonBase.Click 事件。
在 C++/WinRT 中,「如果您想要在 XAML 標記中註冊 OpenButton_Click 方法」,則該方法在實作類型中必須是公用的。 如果您只在命令式程式碼中註冊事件處理常式,則事件處理常式不需要是公用的。
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
或者,您可以讓註冊 XAML 頁面成為實作類型的同伴,並且讓 OpenButton_Click 成為私有的。
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
private:
friend MyPageT;
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
最後一個案例是您要移植的 C# 專案從標記繫結到事件處理常式 (如需更多案例背景,請參閱 Functions in x:Bind)。
<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />
您可以將該標記變更為更簡單的 Click="OpenButton_Click"
。 或者,如果想要的話,也可以保留該標記不變。 為了加以支援,您只需要在 IDL 中宣告事件處理常式。
void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);
注意
將函式宣告為 void
(即使您實作為「射後不理」(Fire and Forget))。
涉及語言語法的變更
類別 | C# | C++/WinRT | 另請參閱 |
---|---|---|---|
存取修飾詞 | public \<member\> |
public: \<member\> |
移植 Button_Click 方法 |
存取資料成員 | this.variable |
this->variable |
|
非同步動作 | async Task ... |
IAsyncAction ... |
IAsyncAction 介面,使用 C++/WinRT 的並行和非同步作業 |
非同步作業 | async Task<T> ... |
IAsyncOperation<T> ... |
IAsyncOperation 介面,使用 C++/WinRT 的並行和非同步作業 |
「射後不理」 (Fire-and-forget) 方法 (暗指非同步) | async void ... |
winrt::fire_and_forget ... |
移植 CopyButton_Click 方法,射後不理 (Fire-and-forget) |
存取列舉常數 | E.Value |
E::Value |
移植 DisplayChangedFormats 方法 |
合作等待 | await ... |
co_await ... |
移植 CopyButton_Click 方法 |
作為私人欄位的預測類型集合 | private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); |
std::vector <MyNamespace::MyRuntimeClass> m_myRuntimeClasses; |
|
GUID 結構 | private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); |
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} }; |
|
命名空間分隔符號 | A.B.T |
A::B::T |
|
Null | null |
nullptr |
移植 UpdateStatus 方法 |
取得類型物件 | typeof(MyType) |
winrt::xaml_typename<MyType>() |
移植 Scenarios 屬性 |
方法的參數宣告 | MyType |
MyType const& |
參數傳遞 |
非同步方法的參數宣告 | MyType |
MyType |
參數傳遞 |
呼叫靜態方法 | T.Method() |
T::Method() |
|
字串 | string 或 System.String |
winrt::hstring | C++/WinRT 中的字串處理 |
字串常值 | "a string literal" |
L"a string literal" |
移植建構函式 Current 和 FEATURE_NAME |
推斷 (或推算) 的類型 | var |
auto |
移植 BuildClipboardFormatsOutputString 方法 |
使用指示詞 | using A.B.C; |
using namespace A::B::C; |
移植建構函式 Current 和 FEATURE_NAME |
逐字/原始字串常值 | @"verbatim string literal" |
LR"(raw string literal)" |
移植 DisplayToast 方法 |
注意
如果標頭檔不包含指定命名空間的 using namespace
指示詞,則您必須完整限定該命名空間的所有類型名稱;或者至少要限定在足以讓編譯器找到這些名稱的範圍。 如需範例,請參閱移植 DisplayToast 方法。
移植類別與成員
針對每個 C# 類型,您都必須決定要將其移植到 Windows 執行階段類型,還是一般 C++ 類別/結構/列舉。 如需詳細資訊,以及說明如何做出這些決策的詳細範例,請參閱將剪貼簿範例從 C# 移植到 C++/WinRT。
C# 屬性通常會成為存取子函式、更動子函式和支援資料成員。 如需詳細資訊和範例,請參閱移植 IsClipboardContentChangedEnabled 屬性。
對於非靜態欄位,請將其設為 實作類型的資料成員。
C# 靜態欄位會成為 C++/WinRT 靜態存取子和 (或) 更動子涵式。 如需詳細資訊和範例,請參閱移植建構函式 Current 和 FEATURE_NAME。
同樣地,對於成員函式,您必須決定每個成員函式是否屬於 IDL,或是否為實作類型的公用或私有成員函式。 如需詳細資訊,以及如何決定的範例,請參閱 MainPage 類型的 IDL。
移植 XAML 標記和資產檔案
在將剪貼簿範例從 C# 移植到 C++/WinRT 案例中,我們可以在 C# 和 C++/WinRT 專案中使用「相同」XAML 標記 (包括資源) 和資產檔案。 在某些情況下,您需要編輯標記來達到此目的。 請參閱複製完成移植 MainPage 所需的 XAML 和樣式。
涉及語言內程序的變更
類別 | C# | C++/WinRT | 另請參閱 |
---|---|---|---|
非同步方法中的存留期管理 | N/A | auto lifetime{ get_strong() }; 或auto lifetime = get_strong(); |
移植 CopyButton_Click 方法 |
處置 | using (var t = v) |
auto t{ v }; t.Close(); // or let wrapper destructor do the work |
移植 CopyImage 方法 |
建構物件 | new MyType(args) |
MyType{ args } 或MyType(args) |
移植 Scenarios 屬性 |
建立未初始化的參考 | MyType myObject; |
MyType myObject{ nullptr }; 或MyType myObject = nullptr; |
移植建構函式 Current 和 FEATURE_NAME |
使用引述將物件建構到變數中 | var myObject = new MyType(args); |
auto myObject{ MyType{ args } }; 或 auto myObject{ MyType(args) }; 或 auto myObject = MyType{ args }; 或 auto myObject = MyType(args); 或 MyType myObject{ args }; 或 MyType myObject(args); |
移植 Footer_Click 方法 |
在無需引數的情況下,將物件建構到變數中 | var myObject = new T(); |
MyType myObject; |
移植 BuildClipboardFormatsOutputString 方法 |
物件初始化速記 | var p = new FileOpenPicker{ ViewMode = PickerViewMode.List }; |
FileOpenPicker p; p.ViewMode(PickerViewMode::List); |
|
大量向量作業 | var p = new FileOpenPicker{ FileTypeFilter = { ".png", ".jpg", ".gif" } }; |
FileOpenPicker p; p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" }); |
移植 CopyButton_Click 方法 |
反復查看集合 | foreach (var v in c) |
for (auto&& v : c) |
移植 BuildClipboardFormatsOutputString 方法 |
攔截例外狀況 | catch (Exception ex) |
catch (winrt::hresult_error const& ex) |
移植 PasteButton_Click 方法 |
例外狀況詳細資料 | ex.Message |
ex.message() |
移植 PasteButton_Click 方法 |
取得屬性值 | myObject.MyProperty |
myObject.MyProperty() |
移植 NotifyUser 方法 |
設定屬性值 | myObject.MyProperty = value; |
myObject.MyProperty(value); |
|
遞增屬性值 | myObject.MyProperty += v; |
myObject.MyProperty(thing.Property() + v); 針對字串,請切換至產生器 |
|
ToString() | myObject.ToString() |
winrt::to_hstring(myObject) |
ToString() |
Windows 執行階段字串的語言字串 | N/A | winrt::hstring{ s } |
|
字串建立 | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
字串建立 |
字串插補 | $"{i++}) {s.Title}" |
winrt::to_hstring 和 (或) winrt::hstring::operator+ | 移植 OnNavigatedTo 方法 |
用於比較的空字串 | System.String.Empty | winrt::hstring::empty | 移植 UpdateStatus 方法 |
建立空白字串 | var myEmptyString = String.Empty; |
winrt::hstring myEmptyString{ L"" }; |
|
字典作業 | map[k] = v; // replaces any existing v = map[k]; // throws if not present map.ContainsKey(k) |
map.Insert(k, v); // replaces any existing v = map.Lookup(k); // throws if not present map.HasKey(k) |
|
類型轉換 (失敗時擲回) | (MyType)v |
v.as<MyType>() |
移植 Footer_Click 方法 |
類型轉換 (失敗時為 null) | v as MyType |
v.try_as<MyType>() |
移植 PasteButton_Click 方法 |
具有 x:Name 的 XAML 元素是屬性 | MyNamedElement |
MyNamedElement() |
移植建構函式 Current 和 FEATURE_NAME |
切換至 UI 執行緒 | CoreDispatcher.RunAsync | CoreDispatcher.RunAsync或 winrt::resume_foreground | 移植 NotifyUser 方法,以及移植 HistoryAndRoaming 方法 |
XAML 頁面中命令式程式碼的 UI 元素結構 | 請參閱 UI 元素結構 | 請參閱 UI 元素結構 |
下列各節將詳細說明資料表中的某些項目。
UI 元素建構
這些程式碼範例示範 XAML 頁面的命令式程式碼中的 UI 元素結構。
var myTextBlock = new TextBlock()
{
Text = "Text",
Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
winrt::unbox_value<Windows::UI::Xaml::Style>(
Resources().Lookup(
winrt::box_value(L"MyTextBlockStyle")
)
)
);
ToString()
C# 類型會提供 Object.ToString 方法。
int i = 2;
var s = i.ToString(); // s is a System.String with value "2".
C++/WinRT 不會直接提供此功能,但您可以轉向使用替代方案。
int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".
C++/WinRT 也針對有限的類型數量支援 winrt::to_hstring。 您必須為想要字串化的任何其他類型新增多載。
語言 | 將 int 字串化 | 將列舉字串化 |
---|---|---|
C# | string result = "hello, " + intValue.ToString(); string result = $"hello, {intValue}"; |
string result = "status: " + status.ToString(); string result = $"status: {status}"; |
C++/WinRT | hstring result = L"hello, " + to_hstring(intValue); |
// must define overload (see below) hstring result = L"status: " + to_hstring(status); |
在將列舉字串化的情況下,您需要提供 winrt::to_hstring 的實作。
namespace winrt
{
hstring to_hstring(StatusEnum status)
{
switch (status)
{
case StatusEnum::Success: return L"Success";
case StatusEnum::AccessDenied: return L"AccessDenied";
case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
default: return to_hstring(static_cast<int>(status));
}
}
}
資料繫結通常會隱含地使用這些字串化作業。
<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>
這些繫結會執行所繫結屬性的 winrt::to_hstring。 在第二個範例 (StatusEnum) 的情況下,您必須提供自己的 winrt::to_hstring 多載,否則會收到編譯器錯誤。
另請參閱移植 Footer_Click 方法。
字串建立
針對字串建立,C# 有內建的 StringBuilder 類型。
類別 | C# | C++/WinRT |
---|---|---|
字串建立 | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
附加 Windows 執行階段字串,保留 null | builder.Append(s); |
builder << std::wstring_view{ s }; |
加入新行 | builder.Append(Environment.NewLine); |
builder << std::endl; |
存取結果 | s = builder.ToString(); |
ws = builder.str(); |
另請參閱移植 BuildClipboardFormatsOutputString 方法,以及移植 DisplayChangedFormats 方法。
在主要 UI 執行緒上執行程式碼
此範例取自條碼掃描器範例。
當您想要在 C# 專案的主要 UI 執行緒上執行作業時,通常會使用 CoreDispatcher.RunAsync 方法,如下所示。
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Do work on the main UI thread here.
});
}
使用 C++/WinRT 來表達更為容易。 請注意,我們接受參數值是假設我們想要在第一個暫止點 (此案例中為 co_await
) 之後存取它們。 如需詳細資訊,請參閱參數傳遞。
winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
co_await Dispatcher();
// Do work on the main UI thread here.
}
如果您需要以預設值以外的優先順序來執行工作,請參閱 winrt::resume_foreground 函式,其有採用 priority 的多載。 如需示範如何等候對 winrt::resume_foreground 呼叫的程式碼範例,請參閱考量使用執行緒親和性程式設計。
C++/WinRT 特定的移植相關工作
定義 IDL 中的執行階段類別
請參閱 MainPage 類型的 IDL 及合併 .idl
檔案。
包含您需要的 C++/WinRT Windows 命名空間標頭檔案
在 C++/WinRT 中,每當您要使用 Windows 命名空間的類型時,您必須包含對應的 C++/WinRT Windows 命名空間標頭檔案。 如需範例,請參閱移植 NotifyUser 方法。
Box 處理和 Unbox 處理
C# 會自動將純量 Box 處理為物件。 C++/WinRT 會要求您明確地呼叫 winrt::box_value 函式。 這兩種語言都需要您明確地進行 Unbox 處理。 請參閱使用 C++/WinRT 進行 Box 處理和 Unbox 處理。
在後續表格中,我們將使用下列定義。
C# | C++/WinRT |
---|---|
int i; |
int i; |
string s; |
winrt::hstring s; |
object o; |
IInspectable o; |
作業 | C# | C++/WinRT |
---|---|---|
Box 處理 | o = 1; o = "string"; |
o = box_value(1); o = box_value(L"string"); |
Unbox 處理 | i = (int)o; s = (string)o; |
i = unbox_value<int>(o); s = unbox_value<winrt::hstring>(o); |
如果您嘗試將 null 指標 Unbox 處理為某個實值類型,則 C++/CX 和 C# 會引發例外狀況。 C++/WinRT 會將此視為程式設計錯誤,並且毀損。 在 C++/WinRT 中,如果您想處理物件不是您所認為類型的情況,請使用 winrt::unbox_value_or 函式。
案例 | C# | C++/WinRT |
---|---|---|
進行已知整數的 Unbox 處理 | i = (int)o; |
i = unbox_value<int>(o); |
如果 o 為 null | System.NullReferenceException |
毀損 |
如果 o 不是已 Box 處理的 int | System.InvalidCastException |
毀損 |
進行 int 的 Unbox 處理,若為 null 則使用遞補;若為其他任何項目則會毀損 | i = o != null ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
可能的話,進行 int 的 Unbox 處理;其他任何項目使用遞補 | i = as int? ?? fallback; |
i = unbox_value_or<int>(o, fallback); |
如需範例,請參閱移植 OnNavigatedTo 方法,以及移植 Footer_Click 方法。
進行字串的 Box 處理和 Unbox 處理
字串在某些方面是實值類型,而在其他方面則是參考類型。 C# 和 C++/WinRT 會以不同的方式處理字串。
ABI 類型 HSTRING 是參考計數字串的指標。 但是它並非衍生自 IInspectable,因此在技術上並不是「物件」。 此外, null HSTRING 代表空字串。 將非衍生自 IInspectable 的項目包裝在 IReference<T>內,即可完成 Box 處理,而 Windows 執行階段會以 PropertyValue 物件形式提供標準實作 (自訂類型會回報為 PropertyType::OtherType)。
C# 表示作為參考類型的 Windows 執行階段字串;而 C++/WinRT 會將字串投影為實值類型。 這表示已進行 Box 處理的 null 字串可以有不同的表示法 (取決於您達成的方式)。
行為 | C# | C++/WinRT |
---|---|---|
宣告 | object o; string s; |
IInspectable o; hstring s; |
字串類型類別 | 參考類型 | 值類型 |
null HSTRING 投影為 | "" |
hstring{} |
Null 和 "" 相同嗎? |
No | Yes |
Null 的有效性 | s = null; s.Length 引發 NullReferenceException |
s = hstring{}; s.size() == 0 (有效) |
如果將 Null 字串指派給物件 | o = (string)null; o == null |
o = box_value(hstring{}); o != nullptr |
如果將 "" 指派給物件 |
o = ""; o != null |
o = box_value(hstring{L""}); o != nullptr |
基本 Box 處理和 Unbox 處理。
作業 | C# | C++/WinRT |
---|---|---|
進行字串的 Box 處理 | o = s; 空字串會變成非 Null 物件。 |
o = box_value(s); 空字串會變成非 Null 物件。 |
進行已知字串的 Unbox 處理 | s = (string)o; Null 物件會變成 Null 字串。 InvalidCastException (如果不是字串)。 |
s = unbox_value<hstring>(o); Null 物件損毀。 如果不是字串,則會損毀。 |
將可能的字串進行 Unbox 處理 | s = o as string; Null 物件或非字串會變成 Null 字串。 OR s = o as string ?? fallback; Null 或非字串會變成遞補。 保留空字串。 |
s = unbox_value_or<hstring>(o, fallback); Null 或非字串會變成遞補。 保留空字串。 |
讓類別可供 {Binding} 標記延伸使用
如果您想要使用 {binding} 標記延伸將資料繫結至您的資料類型,請參閱使用 {Binding} 宣告的繫結物件。
取用 XAML 標記中的物件
在 C# 專案中,您可以使用來自 XAML 標記的私有成員和具名元素。 但是在 C++/WinRT 中,使用 XAML {x:Bind} 標記延伸 取用的所有實體都必須公開於 IDL 中。
此外,布林值的繫結會在 C# 中顯示 true
或 false
,但是在 C++/WinRT 中顯示 Windows.Foundation.IReference`1<Boolean>。
如需詳細資訊和程式碼範例,請參閱使用標記中的物件。
讓資料來源可供 XAML 標記使用
在 C++/WinRT 2.0.190530.8 版或更新版本中,winrt::single_threaded_observable_vector 會建立可觀察的向量,其同時支援 IObservableVector<T> 和 IObservableVector<IInspectable>。 如需範例,請參閱移植 Scenarios 屬性。
您可以撰寫如下所示的 Midl 檔案 (.idl) (另請參閱將執行階段類別分解成 Midl 檔檔案 (.idl))。
namespace Bookstore
{
runtimeclass BookSku { ... }
runtimeclass BookstoreViewModel
{
Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
}
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
其實作方式如下所示。
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...
如需詳細資訊,請參閱 XAML 項目控制項;繫結至 C++/WinRT 集合與使用 C++/WinRT 的集合。
讓資料來源可供 XAML 標記使用 (在 C++/WinRT 2.0.190530.8 之前)
XAML 資料繫結要求項目來源實作 IIterable<IInspectable>,以及下列其中一個介面組合。
- IObservableVector<IInspectable>
- IBindableVector 和 INotifyCollectionChanged
- IBindableVector 和 IBindableObservableVector
- IBindableVector 本身 (不會回應變更)
- IVector<IInspectable>
- IBindableIterable (會逐一查看元素並儲存至私用集合)
在執行階段無法偵測 IVector<T> 等一般介面。 每個 IVector<T> 都有不同的介面識別碼 (IID),這是 T的函數。任何開發人員都可以任意擴充 T 集合,所以顯然 XAML 繫結程式碼永遠不會知道要查詢的完整集合。 該限制不是 C# 的問題,因為每個實作 IEnumerable<T> 的 CLR 物件都會實作 IEnumerable。 在 ABI 層級,這表示每個實作 IObservableVector<T> 的物件都會自動實作 IObservableVector<IInspectable>。
C++/WinRT 不提供該保證。 如果 C++/WinRT 執行階段類別會實作 IObservableVector<T>,我們無法假設也會提供 IObservableVector<IInspectable> 的實作。
因此,前一個範例需要如下所示。
...
runtimeclass BookstoreViewModel
{
// This is really an observable vector of BookSku.
Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}
其實作方式。
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
// This is really an observable vector of BookSku.
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...
如果您需要存取 m_bookSkus 中的物件,則必須將其 QI 回到 Bookstore::BookSku。
Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
for (auto&& obj : m_bookSkus)
{
auto bookSku = obj.as<Bookstore::BookSku>();
if (bookSku.Title() == title) return bookSku;
}
return nullptr;
}
衍生類別
為了從執行階段類別衍生,基底類別必須「可組合」。 C# 不要求您採取任何特殊步驟,即可讓類別變為可組合,而 C++/WinRT 則會要求您採取步驟。 您可使用未密封的關鍵字,指出您希望類別可作為基底類別使用。
unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
...
}
runtimeclass DerivedPage : BasePage
{
...
}
在實作類型的標頭檔案中,您必須先包含基底類別標頭檔,才可包含衍生類別的自動產生標頭。 否則,您會收到錯誤,例如「此類型當作運算式使用並不合法」。
// DerivedPage.h
#include "BasePage.h" // This comes first.
#include "DerivedPage.g.h" // Otherwise this header file will produce an error.
namespace winrt::MyNamespace::implementation
{
struct DerivedPage : DerivedPageT<DerivedPage>
{
...
}
}