Xamarin.Forms Compiled Bindings
Compiled bindings are resolved more quickly than classic bindings, therefore improving data binding performance in Xamarin.Forms applications.
Data bindings have two main problems:
- There's no compile-time validation of binding expressions. Instead, bindings are resolved at runtime. Therefore, any invalid bindings aren't detected until runtime when the application doesn't behave as expected or error messages appear.
- They aren't cost efficient. Bindings are resolved at runtime using general-purpose object inspection (reflection), and the overhead of doing this varies from platform to platform.
Compiled bindings improve data binding performance in Xamarin.Forms 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.
The process for using compiled bindings is to:
- Enable XAML compilation. For more information about XAML compilation, see XAML Compilation.
- Set an
x:DataType
attribute on aVisualElement
to the type of the object that theVisualElement
and its children will bind to.
Note
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.
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 VisualElement
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.
Important
Compiled bindings are currently 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.
Use compiled bindings
The Compiled Color Selector page demonstrates using compiled bindings between Xamarin.Forms views and viewmodel properties:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorSelectorPage"
Title="Compiled Color Selector">
...
<StackLayout x:DataType="local:HslColorViewModel">
<StackLayout.BindingContext>
<local:HslColorViewModel Color="Sienna" />
</StackLayout.BindingContext>
<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>
The root StackLayout
instantiates the HslColorViewModel
and initializes the Color
property within property element tags for the BindingContext
property. This root StackLayout
also defines the x:DataType
attribute as the viewmodel type, indicating that any binding expressions in the root StackLayout
view hierarchy will be compiled. This can be verified by changing any of the binding expressions to bind to a non-existent viewmodel property, which will result in a build error. While this example sets the x:DataType
attribute to a string literal, it can also be set to a type with the x:Type
markup extension. For more information about the x:Type
markup extension, see x:Type Markup Extension.
Important
The x:DataType
attribute can be re-defined at any point in a view hierarchy.
The BoxView
, Label
elements, and Slider
views inherit the binding context from the StackLayout
. These views are all binding targets that reference source properties in the viewmodel. For the BoxView.Color
property, and the Label.Text
property, the data bindings are OneWay
– the properties in the view are set from the properties in the viewmodel. However, the Slider.Value
property uses a TwoWay
binding. This allows each Slider
to be set from the viewmodel, and also for the viewmodel to be set from each Slider
.
When the application is first run, the BoxView
, Label
elements, and Slider
elements are all set from the viewmodel based on the initial Color
property set when the viewmodel was instantiated. This is shown in the following screenshots:
As the sliders are manipulated, the BoxView
and Label
elements are updated accordingly.
For more information about this color selector, see ViewModels and Property-Change Notifications.
Use compiled bindings in a DataTemplate
Bindings in a DataTemplate
are interpreted in the context of the object being templated. Therefore, when using compiled bindings in a DataTemplate
, the DataTemplate
needs to declare the type of its data object using the x:DataType
attribute.
The Compiled Color List page demonstrates using compiled bindings in a DataTemplate
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos"
x:Class="DataBindingDemos.CompiledColorListPage"
Title="Compiled Color List">
<Grid>
...
<ListView x:Name="colorListView"
ItemsSource="{x:Static local:NamedColor.All}"
... >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<ViewCell>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
... />
<Label Text="{Binding FriendlyName}"
... />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- The BoxView doesn't use compiled bindings -->
<BoxView Color="{Binding Source={x:Reference colorListView}, Path=SelectedItem.Color}"
... />
</Grid>
</ContentPage>
The ListView.ItemsSource
property is set to the static NamedColor.All
property. The NamedColor
class uses .NET reflection to enumerate all the static public fields in the Color
structure, and to store them with their names in a collection that is accessible from the static All
property. Therefore, the ListView
is filled with all of the NamedColor
instances. For each item in the ListView
, the binding context for the item is set to a NamedColor
object. The BoxView
and Label
elements in the ViewCell
are bound to NamedColor
properties.
Note that the DataTemplate
defines the x:DataType
attribute to be the NamedColor
type, indicating that any binding expressions in the DataTemplate
view hierarchy will be compiled. This can be verified by changing any of the binding expressions to bind to a non-existent NamedColor
property, which will result in a build error. While this example sets the x:DataType
attribute to a string literal, it can also be set to a type with the x:Type
markup extension. For more information about the x:Type
markup extension, see x:Type Markup Extension.
When the application is first run, the ListView
is populated with NamedColor
instances. When an item in the ListView
is selected, the BoxView.Color
property is set to the color of the selected item in the ListView
:
Selecting other items in the ListView
updates the color of the BoxView
.
Combine compiled bindings with classic bindings
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 DataTemplate
use compiled bindings, while the BoxView
that's set to the color selected in the ListView
does not.
Careful structuring of x:DataType
attributes can therefore lead to a page using compiled and classic bindings. Alternatively, the x:DataType
attribute can be re-defined at any point in a view hierarchy to null
using the x:Null
markup extension. Doing this indicates that any binding expressions within the view hierarchy will use classic bindings. The Mixed Bindings page demonstrates this approach:
<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>
The root StackLayout
sets the x:DataType
attribute to be the HslColorViewModel
type, indicating that any binding expression in the root StackLayout
view hierarchy will be compiled. However, the inner StackLayout
redefines the x:DataType
attribute to null
with the x:Null
markup expression. Therefore, the binding expressions within the inner StackLayout
use classic bindings. Only the BoxView
, within the root StackLayout
view hierarchy, uses compiled bindings.
For more information about the x:Null
markup expression, see x:Null Markup Extension.
Performance
Compiled bindings improve data binding performance, with the performance benefit varying. Unit testing reveals that:
- A compiled binding that uses property-change notification (i.e. a
OneWay
,OneWayToSource
, orTwoWay
binding) is resolved approximately 8 times quicker than a classic binding. - A compiled binding that doesn't use property-change notification (i.e. a
OneTime
binding) is resolved approximately 20 times quicker than a classic binding. - Setting the
BindingContext
on a compiled binding that uses property change notification (i.e. aOneWay
,OneWayToSource
, orTwoWay
binding) is approximately 5 times quicker than setting theBindingContext
on a classic binding. - Setting the
BindingContext
on a compiled binding that doesn't use property change notification (i.e. aOneTime
binding) is approximately 7 times quicker than setting theBindingContext
on a classic binding.
These performance differences can be magnified on mobile devices, dependent upon the platform being used, the version of the operating system being used, and the device on which the application is running.