diff --git a/docs/fundamentals/data-binding/basic-bindings.md b/docs/fundamentals/data-binding/basic-bindings.md index fc2febe76..fd6b7f832 100644 --- a/docs/fundamentals/data-binding/basic-bindings.md +++ b/docs/fundamentals/data-binding/basic-bindings.md @@ -1,7 +1,7 @@ --- title: "Basic bindings" description: ".NET MAUI data binding links a pair of properties between two objects, at least one of which is usually a user-interface object. These two objects are called the target and the source." -ms.date: 01/19/2022 +ms.date: 09/26/2024 --- # Basic bindings @@ -119,6 +119,9 @@ XAML markup extensions such as `x:Reference` and `Binding` can have *content pro Rotation="{Binding Value}" /> ``` +> [!IMPORTANT] +> Binding performance can be improved by using compiled bindings. For more information, see [Compiled bindings](compiled-bindings.md). + ## Bindings without a binding context The `BindingContext` property is an important component of data bindings, but it is not always necessary. The source object can instead be specified in the `SetBinding` call or the `Binding` markup extension: diff --git a/docs/fundamentals/data-binding/compiled-bindings.md b/docs/fundamentals/data-binding/compiled-bindings.md index ca1705e16..16f73077f 100644 --- a/docs/fundamentals/data-binding/compiled-bindings.md +++ b/docs/fundamentals/data-binding/compiled-bindings.md @@ -15,17 +15,30 @@ ms.date: 09/27/2024 Compiled bindings improve data binding performance in .NET MAUI applications by resolving binding expressions at compile-time rather than runtime. In addition, this compile-time validation of binding expressions enables a better developer troubleshooting experience because invalid bindings are reported as build errors. -To use compiled bindings, set an `x:DataType` attribute on a to the type of the object that the and its children will bind to. It's recommended to set the `x:DataType` attribute at the same level in the view hierarchy as the `BindingContext` is set. However, this attribute can be re-defined at any location in a view hierarchy. +::: moniker range=">=net-maui-9.0" + +> [!IMPORTANT] +> Compiled bindings are required instead of string-based bindings in NativeAOT apps, and in apps with full trimming enabled. + +::: moniker-end + +## Compiled bindings in XAML + +To use compiled bindings in XAML, set an `x:DataType` attribute on a to the type of the object that the and its children will bind to. It's recommended to set the `x:DataType` attribute at the same level in the view hierarchy as the `BindingContext` is set. However, this attribute can be re-defined at any location in a view hierarchy. > [!NOTE] > Compiled bindings require the use of XAML compilation, which is enabled by default in .NET MAUI. If you've disabled XAML compilation, you'll need to enable it. For more information, see [XAML Compilation](~/xaml/xamlc.md). -To use compiled bindings, the `x:DataType` attribute must be set to a string literal, or a type using the `x:Type` markup extension. At XAML compile time, any invalid binding expressions will be reported as build errors. However, the XAML compiler will only report a build error for the first invalid binding expression that it encounters. Any valid binding expressions that are defined on the or its children will be compiled, regardless of whether the `BindingContext` is set in XAML or code. Compiling a binding expression generates compiled code that will get a value from a property on the *source*, and set it on the property on the *target* that's specified in the markup. In addition, depending on the binding expression, the generated code may observe changes in the value of the *source* property and refresh the *target* property, and may push changes from the *target* back to the *source*. +To use compiled bindings in XAML, the `x:DataType` attribute must be set to a string literal, or a type using the `x:Type` markup extension. At XAML compile time, any invalid binding expressions will be reported as build errors. However, the XAML compiler will only report a build error for the first invalid binding expression that it encounters. Any valid binding expressions that are defined on the or its children will be compiled, regardless of whether the `BindingContext` is set in XAML or code. Compiling a binding expression generates compiled code that will get a value from a property on the *source*, and set it on the property on the *target* that's specified in the markup. In addition, depending on the binding expression, the generated code may observe changes in the value of the *source* property and refresh the *target* property, and may push changes from the *target* back to the *source*. + +::: moniker range="=net-maui-8.0" > [!IMPORTANT] -> Compiled bindings are disabled for any binding expressions that define the `Source` property. This is because the `Source` property is always set using the `x:Reference` markup extension, which can't be resolved at compile time. +> Compiled bindings are disabled for any XAML binding expressions that define the `Source` property. This is because the `Source` property is always set using the `x:Reference` markup extension, which can't be resolved at compile time. > -> In addition, compiled bindings are currently unsupported on multi-bindings. +> In addition, compiled bindings in XAML are currently unsupported on multi-bindings. + +::: moniker-end By default, .NET MAUI doesn't produce build warnings for bindings that don't use compiled bindings, unless you've enabled NativeAOT for your app. However, you can opt into compiled bindings warnings being produced by setting the `$(MauiStrictXamlCompilation)` build property to `true` in your app's project file (*.csproj): @@ -33,7 +46,7 @@ By default, .NET MAUI doesn't produce build warnings for bindings that don't use true ``` -## Use compiled bindings +### Use compiled bindings in XAML The following example demonstrates using compiled bindings between .NET MAUI views and viewmodel properties: @@ -77,7 +90,7 @@ When the example is first run, the , are interpreted in the context of the object being templated. Therefore, when using compiled bindings in a , the needs to declare the type of its data object using the `x:DataType` attribute. @@ -124,7 +137,7 @@ When the example is first run, the is po Selecting other items in the updates the color of the . -## Combine compiled bindings with classic bindings +### Combine compiled bindings with classic bindings in XAML Binding expressions are only compiled for the view hierarchy that the `x:DataType` attribute is defined on. Conversely, any views in a hierarchy on which the `x:DataType` attribute is not defined will use classic bindings. It's therefore possible to combine compiled bindings and classic bindings on a page. For example, in the previous section the views within the use compiled bindings, while the that's set to the color selected in the does not. @@ -154,6 +167,63 @@ The root sets the `x:DataType` attrib For more information about the `x:Null` markup expression, see [x:Null Markup Extension](~/xaml/markup-extensions/consume.md#xnull-markup-extension). +::: moniker range=">=net-maui-9.0" + +## Compiled bindings in code + +Bindings written in code typically use string paths that are resolved at runtime with reflection. However, the extension method also has an overload that defines bindings using a `Func` argument instead of a string path: + +```csharp +MyLabel.SetBinding(Label.TextProperty, static (Entry entry) => entry.Text); +``` + +Not all methods can be used to define a compiled binding. The expression must be a simple property access expression. The following examples show valid and invalid binding expressions: + +```csharp +// 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}"; +``` + +In addition, the method sets the binding directly on the object with a `Func`, and returns the binding object instance: + +```csharp +myEntry.SetBinding(Entry.TextProperty, new MultiBinding +{ + Bindings = new Collection + { + 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() +}); +``` + +These compiled binding approaches provide the following benefits: + +- Improved data binding performance by resolving binding expressions at compile-time rather than runtime. +- A better developer troubleshooting experience because invalid bindings are reported as build errors. +- Intellisense while editing. + +::: moniker-end + ## Performance Compiled bindings improve data binding performance, with the performance benefit varying: diff --git a/docs/whats-new/dotnet-9.md b/docs/whats-new/dotnet-9.md index bd1b4d6bf..770f4cd76 100644 --- a/docs/whats-new/dotnet-9.md +++ b/docs/whats-new/dotnet-9.md @@ -231,7 +231,82 @@ public static class MauiProgram } ``` -## Compiled bindings +## Compiled bindings in code + +Bindings written in code typically use string paths that are resolved at runtime with reflection, and the overhead of doing this varies from platform to platform. .NET MAUI 9 introduces an additional extension method that defines bindings using a `Func` argument instead of a string path: + +```csharp +// in .NET 8 +MyLabel.SetBinding(Label.TextProperty, "Text"); + +// in .NET 9 +MyLabel.SetBinding(Label.TextProperty, static (Entry entry) => entry.Text); +``` + +This compiled binding approach provides the following benefits: + +- Improved data binding performance by resolving binding expressions at compile-time rather than runtime. +- A better developer troubleshooting experience because invalid bindings are reported as build errors. +- Intellisense while editing. + +Not all methods can be used to define a compiled binding. The expression must be a simple property access expression. The following examples show valid and invalid binding expressions: + +```csharp +// 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}"; +``` + +In addition, .NET MAUI 9 adds a method that sets the binding directly on the object with a `Func`, and returns the binding object instance: + +```csharp +// in .NET 8 +myEntry.SetBinding(Entry.TextProperty, new MultiBinding +{ + Bindings = new Collection + { + new Binding(nameof(Entry.FontFamily), source: RelativeBindingSource.Self), + new Binding(nameof(Entry.FontSize), source: RelativeBindingSource.Self), + new Binding(nameof(Entry.FontAttributes), source: RelativeBindingSource.Self), + }, + Converter = new StringConcatenationConverter() +}); + +// in .NET 9 +myEntry.SetBinding(Entry.TextProperty, new MultiBinding +{ + Bindings = new Collection + { + 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() +}); +``` + +> [!IMPORTANT] +> Compiled bindings are required instead of string-based bindings in NativeAOT apps, and in apps with full trimming enabled. + +## Compiled bindings in XAML + +In .NET MAUI 8, compiled bindings are disabled for any XAML binding expressions that define the `Source` property, and are unsupported on multi-bindings. These restrictions have been removed in .NET MAUI 9. By default, .NET MAUI doesn't produce build warnings for bindings that don't use compiled bindings, unless you've enabled NativeAOT for your app. However, you can opt into compiled bindings warnings being produced by setting the `$(MauiStrictXamlCompilation)` build property to `true` in your app's project file (*.csproj):