Скомпилированные привязки

Browse sample. Обзор примера

Привязки данных .NET Multi-platform App UI (.NET MAUI) имеют две основные проблемы:

  1. Не выполняется проверка выражений привязки во время компиляции. Вместо этого привязки разрешаются во время выполнения. Таким образом, недопустимые привязки проявляются только во время выполнения в виде нарушений в поведении приложения или сообщений об ошибке.
  2. Затрачиваются избыточные ресурсы. Привязки разрешаются во время выполнения с использованием проверки объектов общего назначения (отражение), причем объем затрачиваемых на это ресурсов зависит от платформы.

Скомпилированные привязки повышают производительность привязки данных в приложениях .NET MAUI, разрешая выражения привязки во время компиляции, а не во время выполнения. Кроме того, такая проверка выражений привязки во время компиляции позволяет повысить эффективность устранения неполадок, поскольку недопустимые привязки выявляются как ошибки построения.

Чтобы использовать скомпилированные привязки, задайте x:DataType атрибут для VisualElement типа объекта, к которому VisualElement привязываются его дочерние элементы. Атрибут x:DataType рекомендуется задавать на том же уровне иерархии представления, на котором задается BindingContext. Однако этот атрибут может быть переопределен в любом месте иерархии представлений.

Примечание.

Скомпилированные привязки требуют использования компиляции XAML, которая включена по умолчанию в .NET MAUI. Если вы отключили компиляцию XAML, ее необходимо включить. Дополнительные сведения см. в разделе Компиляция XAML.

Чтобы использовать скомпилированные привязки, атрибуту x:DataType необходимо назначить строковый литерал или тип, использующий расширение разметки x:Type. Во время компиляции XAML любые недопустимые выражения привязки будут выявляться как ошибки построения. Тем не менее компилятор XAML будет возвращать ошибку построения только для первого обнаруженного недопустимого выражения привязки. Любые допустимые выражения привязки, которые определены для объекта VisualElement или его дочерних объектов, будут скомпилированы независимо от того, задается ли BindingContext в XAML или в коде. При компиляции выражения привязки создается скомпилированный код, который получает значение из свойства источника и присваивает его свойству адресата, который указан в разметке. Кроме того, в зависимости от выражения привязки созданный код может наблюдать изменения в значении свойства источника и обновляет свойство адресата, а также может отправлять изменения из адресата обратно в источник.

Важно!

Скомпилированные привязки отключены для любых выражений привязки, определяющих Source свойство. Это связано с тем, что свойство Source всегда задается с использованием расширения разметки x:Reference, которое не может быть разрешено во время компиляции.

Кроме того, скомпилированные привязки в настоящее время не поддерживаются в нескольких привязках.

Использование скомпилированных привязок

В следующем примере показано использование скомпилированных привязок между представлениями .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 будут скомпилированы. Это можно проверить, изменив любое выражение привязки так, чтобы оно было привязано к несуществующему свойству модели представления, что приведет к ошибке сборки. Хотя в этом примере атрибуту x:DataType присваивается строковый литерал, для него также можно задать тип с расширением разметки x:Type. Дополнительные сведения о расширении разметки x:Type см. в разделе Расширение разметки x:Type.

Важно!

Обратите внимание, что атрибут x:DataType может быть переопределен в любом месте иерархии представления.

Элементы BoxView, Label и представления Slider наследуют контекст привязки из представления ContentPage. Эти представления являются целевыми объектами привязки, которые ссылаются на свойства источника в модели представления. Для свойств BoxView.Color и Label.Text привязки данных имеют режим OneWay: свойства в представлении задаются по свойствам в модели представления. Тем не менее свойство Slider.Value использует привязку TwoWay. Поэтому каждый объект Slider задается из модели представления, а модель представления задается из каждого объекта Slider.

При первом запуске BoxViewLabel примера элементы и Slider элементы задаются из модели представления на основе начального Color свойства, заданного при создании экземпляра модели представления. По мере управления BoxViewLabel ползунками элементы обновляются соответствующим образом:

Compiled color selector.

Дополнительные сведения об этом селекторе цветов см. в разделе ViewModels и уведомления об изменении свойств.

Использование скомпилированных привязок в DataTemplate

Привязки в DataTemplate интерпретируются в контексте объекта, к которому применяется шаблон. Таким образом, при использовании скомпилированных привязок в DataTemplate объект DataTemplate должен объявлять тип своего объекта данных с использованием атрибута x:DataType.

В следующем примере показано использование скомпилированных привязок в :DataTemplate

<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.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>

Свойство ListView.ItemsSource получает статическое свойство NamedColor.All. Класс NamedColor использует отражение .NET для перечисления всех статических открытых полей в Colors классе и хранения их имен в коллекции, доступной из статического All свойства. Таким образом, объект ListView заполняется экземплярами NamedColor. Для каждого элемента в ListView в качестве контекста привязки для элемента задается объект NamedColor. Элементы BoxView и Label в объекте ViewCell привязываются к свойствам NamedColor.

Определяет DataTemplate атрибут NamedColor типа, указывающий, что любые выражения привязки в DataTemplate иерархии представлений будут x:DataType скомпилированы. Это можно проверить, изменив любое выражение привязки так, чтобы оно было привязано к несуществующему свойству NamedColor, что приведет к ошибке построения. Хотя в этом примере атрибуту x:DataType присваивается строковый литерал, для него также можно задать тип с расширением разметки x:Type. Дополнительные сведения о расширении разметки x:Type см. в разделе Расширение разметки x:Type.

При первом запуске ListView примера заполняется NamedColor экземплярами. При выборе элемента в объекте ListView свойству BoxView.Color присваивается цвет элемента, выбранного в ListView:

Compiled color list.

При выборе других элементов в ListView обновляется цвет элемента BoxView.

Объединение скомпилированных привязок с классическими привязками

Выражения привязки компилируются только для иерархии представления, в которой определен атрибут 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.

Производительность

Скомпилированные привязки повышают производительность привязки данных с различными преимуществами производительности:

  • Скомпилированные привязки, использующие уведомление об изменении свойства (то есть привязки в режиме OneWay, OneWayToSource или TwoWay), разрешаются примерно в 8 раз быстрее относительно классических привязок.
  • Скомпилированные привязки, не использующие уведомление об изменении свойства (то есть привязки в режиме OneTime), разрешаются примерно в 20 раз быстрее относительно классических привязок.
  • Задание BindingContext для скомпилированных привязок, использующих уведомление об изменении свойства (то есть для привязок в режиме OneWay, OneWayToSource или TwoWay), выполняется примерно в 5 раз быстрее, чем задание BindingContext для классических привязок.
  • Задание BindingContext для скомпилированных привязок, не использующих уведомление об изменении свойства (то есть для привязок в режиме OneTime), выполняется примерно в 7 раз быстрее, чем задание BindingContext для классических привязок.

Выигрыш в производительности может быть еще более весомым на мобильных устройствах в зависимости от используемой платформы, версии операционной системы и типа устройства, на котором выполняется приложение.