編譯的系結
.NET 多平臺應用程式 UI (.NET MAUI) 數據系結有兩個主要問題:
- 無法在編譯時間驗證繫結運算式。 相反地,繫結是在執行階段進行解析。 因此,當應用程式不如預期般運作或出現錯誤訊息時,要到執行階段才會偵測到任何無效的繫結。
- 不符合成本效益。 繫結使用一般用途物件檢查 (反射) 在執行階段進行解析,而執行此作業的成本會因平台而異。
編譯的系結可藉由在編譯時期解析系結運算式,而不是運行時間,來改善 .NET MAUI 應用程式中的數據系結效能。 此外,在編譯時間驗證繫結運算式可改善開發人員的疑難排解體驗,因為無效的繫結會回報為建置錯誤。
重要
需要編譯的系結,而不是 NativeAOT 應用程式中的字串型系結,以及在已啟用完整修剪的應用程式中。 如需詳細資訊,請參閱修剪 .NET MAUI 應用程式和原生 AOT 部署。
XAML 中的已編譯系結
若要在 XAML 中使用已編譯的系結,請將 上的 x:DataType
屬性設定VisualElement為 和其子系系所系結的物件VisualElement類型。 建議在檢視階層架構中與設定 x:DataType
相同的層級設定 BindingContext 屬性。 不過,這個屬性可以在檢視階層中的任何位置重新定義。
重要
編譯的系結需要使用預設在 .NET MAUI 中啟用的 XAML 編譯。 如果您已停用 XAML 編譯,則必須啟用它。 如需詳細資訊,請參閱 XAML 編譯。
若要在 XAML 中使用已編譯的系結, x:DataType
屬性必須設定為字串常值,或使用標記延伸的類型 x:Type
。 在 XAML 編譯期間,任何無效的繫結運算式都會回報為建置錯誤。 不過,XAML 編譯器只會在第一次遇到無效的繫結運算式時回報建置錯誤。 定義於 VisualElement 或其子系的任何有效繫結運算式都會經過編譯,無論 BindingContext 是在 XAML 或程式碼中設定。 編譯繫結運算式會產生經過編譯的程式碼,該程式碼會從「來源」屬性取得一個值,並在標記中指定的「目標」屬性上進行設定。 此外,視繫結運算式而定,產生的程式碼可以在「來源」屬性值中觀察變更並重新整理「目標」屬性,也可以將變更從「目標」推送回到「來源」。
重要
已編譯的系結會針對定義 Source
屬性的任何 XAML 系結表達式停用。 這是因為 Source
屬性一律使用 x:Reference
標記延伸設定,因此無法在編譯時間進行解析。
此外,XAML 中的已編譯系結目前不支援多重系結。
根據預設,.NET MAUI 不會針對不使用已編譯系結的 XAML 系結產生建置警告。 不過,您可以選擇加入編譯的系結警告,方法是將建置屬性$(MauiStrictXamlCompilation)
設定true
為在應用程式的項目檔中 (*.csproj):
<MauiStrictXamlCompilation>true</MauiStrictXamlCompilation>
根據預設,.NET MAUI 會針對不使用已編譯系結的 XAML 系結產生建置警告。
如需 XAML 編譯系結警告的詳細資訊,請參閱 XAML 編譯的系結警告。
在 XAML 中使用編譯的系結
下列範例示範如何在 .NET MAUI 檢視和 viewmodel 屬性之間使用已編譯的系結:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorSelectorPage"
x:DataType="local:HslColorViewModel"
Title="Compiled Color Selector">
<ContentPage.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</ContentPage.BindingContext>
...
<StackLayout>
<BoxView Color="{Binding Color}"
... />
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
</ContentPage>
具ContentPage現化 ,HslColorViewModel
並在 屬性的屬性項目標記Color
內初始化 BindingContext 屬性。
ContentPage也會將 屬性定義為 x:DataType
viewmodel 類型,表示將編譯檢視階層中的任何ContentPage系結表達式。 您可以藉由變更任何系結表達式以系結至不存在的 viewmodel 屬性來驗證此情況,這會導致建置錯誤。 雖然這個範例會將 x:DataType
屬性設定為字串常值,但它也可以設定為具有標記延伸的類型 x:Type
。 如需標記延伸的詳細資訊 x:Type
,請參閱 x:Type 標記延伸。
重要
您可以在檢視階層架構中的任何位置重新定義 x:DataType
屬性。
BoxView、Label 項目和 Slider 檢視會繼承 ContentPage 的繫結內容。 這些檢視都是參考 viewmodel 中來源屬性的系結目標。
BoxView.Color
針對 屬性和 Label.Text
屬性,數據系結為 OneWay
– 檢視中的屬性會從 viewmodel 中的屬性設定。 不過,Slider.Value
屬性使用 TwoWay
繫結。 這可讓每一個 Slider 都從 viewmodel 進行設定,也允許從每個 Slider設定 viewmodel。
第一次執行範例時,BoxView會根據 ViewModel 具現化時所設定的初始Label屬性,從 viewmodel 設定 、 Slider 元素和Color
元素。 當滑桿作時, BoxView 和 Label 元素會據以更新:
如需此色彩選取器的詳細資訊,請參閱 ViewModels 和屬性變更通知。
在 DataTemplate 中使用 XAML 中的已編譯系結
DataTemplate 中的繫結是在所要樣板化的物件內容中進行解譯。 因此,在 DataTemplate 中使用編譯的繫結時,DataTemplate 必須使用 x:DataType
屬性來宣告其資料物件類型。 若無法執行這項操作,可能會導致 DataTemplate 繼承其父範圍中的不正確 x:DataType
:
<ContentPage ...
x:DataType="local:AnimalsPageViewModel">
<!-- Binding to AnimalsPageViewModel.Animals -->
<CollectionView ItemsSource="{Binding Animals}">
<CollectionView.ItemTemplate>
<DataTemplate>
<!-- incorrect: compiler thinks you want to bind to AnimalsPageViewModel.Name -->
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
下列範例示範如何在 上x:DataType
正確設定 DataTemplate :
<ContentPage ...
x:DataType="local:AnimalsPageViewModel">
<!-- Binding to AnimalsPageViewModel.Animals -->
<CollectionView ItemsSource="{Binding Animals}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:Animal">
<!-- correct: compiler knows you want to bind to Animal.Name -->
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
雖然這個範例會將 x:DataType
屬性設定為字串常值,但它也可以設定為具有標記延伸的類型 x:Type
。 如需標記延伸的詳細資訊 x:Type
,請參閱 x:Type 標記延伸。
編譯定義 屬性的 Source
系結
在 .NET MAUI 9 之前,XAML 編譯程式會略過定義 Source
屬性而非 的 BindingContext系結編譯。 您可以從 .NET MAUI 9 編譯這些系結,以利用更好的運行時間效能。 不過,預設不會啟用此優化,以避免中斷現有的應用程式程序代碼。 若要開啟此優化,請將 $(MauiEnableXamlCBindingWithSourceCompilation)
應用程式的項目檔中的組建屬性設定為 true
:
<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
然後,請確定所有系結都以正確的 x:DataType
標註,而且它們不會從其父範圍繼承不正確的數據類型:
<HorizontalStackLayout BindingContext="{x:Reference slider}" x:DataType="Slider">
<Label Text="{Binding Value}" />
<Label Text="{Binding Text, Source={x:Reference entry}, x:DataType=Entry}" />
</HorizontalStackLayout>
注意
如果具有的Source
系結,但繼承x:DataType
自父系的 ,則和 型別x:DataType
之間Source
可能會不符。 在此案例中,將會產生警告,並回復回反映型系結,以在運行時間解析系結路徑。
在 XAML 中結合已編譯的系結與傳統系結
只有在已定義 x:DataType
屬性的檢視階層架構中才能編譯繫結運算式。 相反地,未定義 x:DataType
屬性之階層架構中的任何檢視則會使用傳統繫結。 因此,您可以將編譯的繫結與傳統繫結合併成一頁。 例如,在上一節中,DataTemplate 中的檢視使用所編譯繫結,而設定為 BoxView 中所選色彩的 ListView 則否。
因此,您可以謹慎建構 x:DataType
屬性,來產生同時使用編譯繫結和傳統繫結的頁面。 或者,您可以在檢視階層架構中的任何位置,使用 x:DataType
標記延伸將 null
屬性重新定義為 x:Null
。 這樣做表示檢視階層架構中的任何繫結運算式都會使用傳統繫結。 下列範例示範此方法:
<StackLayout x:DataType="local:HslColorViewModel">
<StackLayout.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</StackLayout.BindingContext>
<BoxView Color="{Binding Color}"
VerticalOptions="FillAndExpand" />
<StackLayout x:DataType="{x:Null}"
Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Hue}" />
<Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
<Slider Value="{Binding Saturation}" />
<Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
<Slider Value="{Binding Luminosity}" />
<Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
</StackLayout>
</StackLayout>
根 StackLayout 會將 x:DataType
屬性設定為 HslColorViewModel
類型,表示根 StackLayout 檢視階層架構中的任何繫結運算式都會經過編譯。 不過,內部 StackLayout 會使用 x:DataType
標記延伸將 null
屬性重新定義為 x:Null
。 因此,內部 StackLayout 中的繫結運算式會使用傳統繫結。 只有根 BoxView 檢視階層架構中的 StackLayout 會使用所編譯繫結。
如需 x:Null
標記延伸的詳細資訊,請參閱 x:Null 標記延伸。
XAML 編譯的系結警告
下表列出已編譯系結的編譯程式警告,以及如何加以解析:
代碼 | 訊息 | Fix |
---|---|---|
XC0022 |
如果已指定,可以編譯系結以改善運行時間效能 x:DataType 。 |
將 新增 x:DataType 至 XAML 以指定目前 BindingContext的類型。 最佳做法是新增 x:DataType 至系結內容變更的所有元素。 |
XC0023 |
如果 x:DataType 不是明確 null ,可以編譯系結以改善運行時間效能。 |
將取代 x:DataType="{x:Null}" 為正確的類型。 |
代碼 | 訊息 |
---|---|
XC0022 |
如果已指定,可以編譯系結以改善運行時間效能 x:DataType 。 若要修正此警告,請將 新增 x:DataType 至您的 XAML 以指定目前 BindingContext的類型。 最佳做法是新增 x:DataType 至系結內容變更的所有元素。 |
XC0023 |
如果 x:DataType 不是明確 null ,可以編譯系結以改善運行時間效能。 若要修正此警告,請將 取代 x:DataType="{x:Null}" 為正確的類型。 |
XC0024 |
由於批注來自外部範圍, x:DataType 因此系結可能編譯不正確。 請確定您已使用正確的DataTemplate 標註所有 x:DataType XAML 元素。 若要修正此警告,請確定所有 DataTemplate 元素都以正確的 x:DataType 標註。 |
XC0025 |
未編譯系結,因為它具有明確設定 Source 的屬性,且未啟用與 Source 的系結編譯。 請考慮在項目檔中設定 <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> 來啟用此優化,並確定已為此系結指定正確的 x:DataType 。 若要修正此警告,請在專案檔中啟用 $(MauiEnableXamlCBindingWithSourceCompilation) 建置屬性,並使用適當的 x:DataType 來標註所有系結。 |
若要確保不會忽略這些警告,請考慮變更特定警告,以使用 $(WarningsAsErrors)
建置屬性建置錯誤:
<WarningsAsErrors>$(WarningsAsErrors);XC0022;XC0023</WarningsAsErrors>
若要忽略這些警告,請使用具有特定警告碼的 $(NoWarn)
build 屬性:
<NoWarn>$(NoWarn);XC0022;XC0023</NoWarn>
重要
XC0022
除非建置屬性設定為 XC0023
,否則一律會隱藏 $(MauiStrictXamlCompilation)
和 true
警告。
如果您在應用程式的項目檔中將 $(TreatWarningsAsErrors)
建置屬性設定為 true
,但您想要忽略某些 XAML 編譯程式警告,請使用 $(NoWarn)
build 屬性來讓這些警告 $(WarningsNotAsErrors)
或組建屬性無回應,以減少某些特定程式代碼的嚴重性。
根據預設,.NET MAUI 會針對不使用已編譯系結的 XAML 系結產生建置警告。 您可以選擇加入編譯的系結警告,方法是在應用程式的項目檔中將 和 $(MauiStrictXamlCompilation)
建置屬性$(TreatWarningsAsErrors)
設定true
為 ,以被視為錯誤:
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<MauiStrictXamlCompilation>true</MauiStrictXamlCompilation>
注意
根據預設, $(MauiStrictXamlCompilation)
除非您使用完整修剪或 NativeAOT 發佈應用程式,否則組建屬性為 false
。
程序代碼中已編譯的系結
以程式代碼撰寫的系結通常會使用以反映在運行時間解析的字串路徑。 不過,擴充方法也有一個多載, SetBinding 可定義使用 Func
自變數而非字串路徑的系結:
MyLabel.SetBinding(Label.TextProperty, static (Entry entry) => entry.Text);
並非所有方法都可以用來定義已編譯的系結。 表達式必須是簡單的屬性存取表達式。 下列範例顯示有效和無效的系結表示式:
// Valid: Property access
static (PersonViewModel vm) => vm.Name;
static (PersonViewModel vm) => vm.Address?.Street;
// Valid: Array and indexer access
static (PersonViewModel vm) => vm.PhoneNumbers[0];
static (PersonViewModel vm) => vm.Config["Font"];
// Valid: Casts
static (Label label) => (label.BindingContext as PersonViewModel).Name;
static (Label label) => ((PersonViewModel)label.BindingContext).Name;
// Invalid: Method calls
static (PersonViewModel vm) => vm.GetAddress();
static (PersonViewModel vm) => vm.Address?.ToString();
// Invalid: Complex expressions
static (PersonViewModel vm) => vm.Address?.Street + " " + vm.Address?.City;
static (PersonViewModel vm) => $"Name: {vm.Name}";
警告
如果屬性或索引器的 set 存取子無法存取,將會發生編譯錯誤 CS0272。 如果發生這種情況,請增加存取子的可存取性。
此外,方法會 BindingBase.Create 使用 直接在 對象 Func
上設定系結,並傳回系結物件實例:
myEntry.SetBinding(Entry.TextProperty, new MultiBinding
{
Bindings = new Collection<BindingBase>
{
Binding.Create(static (Entry entry) => entry.FontFamily, source: RelativeBindingSource.Self),
Binding.Create(static (Entry entry) => entry.FontSize, source: RelativeBindingSource.Self),
Binding.Create(static (Entry entry) => entry.FontAttributes, source: RelativeBindingSource.Self),
},
Converter = new StringConcatenationConverter()
});
這些編譯的系結方法提供下列優點:
- 藉由在編譯階段解析系結表達式而非運行時間,以改善數據系結效能。
- 較佳的開發人員疑難解答體驗,因為無效的系結會回報為建置錯誤。
- 編輯時 Intellisense。
效能
編譯的系結可改善數據系結效能,效能優點不同:
- 使用屬性變更通知之編譯繫結 (例如
OneWay
、OneWayToSource
或TwoWay
繫結) 的解析速度,大致上比傳統繫結快 8 倍。 - 不使用屬性變更通知之編譯繫結 (例如
OneTime
繫結) 的解析速度,大致上比傳統繫結快 20 倍。 - 在使用屬性變更通知的編譯繫結 (例如 BindingContext、
OneWay
或OneWayToSource
繫結) 上設定TwoWay
,大致上比在傳統繫結上設定 BindingContext 快 5 倍。 - 在不使用屬性變更通知的編譯繫結 (例如 BindingContext 繫結) 上設定
OneTime
,大致上比在傳統繫結上設定 BindingContext 快 7 倍。
這些效能差異在行動裝置上可能會更大,視所使用的平台、所使用的作業系統版本,以及執行應用程式的裝置而定。