Поделиться через


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

Просмотрите пример. Обзор примера

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

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

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

Внимание

Скомпилированные привязки требуются вместо строковых привязок в приложениях NativeAOT и в приложениях с поддержкой полной обрезки. Дополнительные сведения см. в разделе об обрезке приложения .NET MAUI и развертывания Собственного AOT.

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

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

Внимание

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

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

Внимание

Скомпилированные привязки отключены для любых выражений привязки XAML, определяющих Source свойство. Это связано с тем, что свойство 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 будут скомпилированы. Это можно проверить, изменив любое выражение привязки так, чтобы оно было привязано к несуществующему свойству модели представления, что приведет к ошибке сборки. Хотя в этом примере атрибуту 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 свойства, заданного при создании экземпляра модели представления. По мере управления BoxView Label ползунками элементы обновляются соответствующим образом:

Скомпилированный селектор цветов.

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

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

Привязки в 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 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:

Скомпилированный список цветов.

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

Компиляция привязок, определяющих 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 элементы XAML с правильным x:DataTypeзначением.

Чтобы устранить это предупреждение, убедитесь, что все DataTemplate элементы помечены правильно x:DataType.
XC0025 Привязка не была скомпилирована, так как она имеет явно заданное Source свойство и компиляция привязок с Source не включена. Рассмотрите возможность включения этой оптимизации, задав <MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation> в файле проекта правильный параметр и убедитесь, что для этой привязки задано правильное x:DataType значение.

Чтобы устранить это предупреждение, включите свойство сборки в файле проекта и заметьте $(MauiEnableXamlCBindingWithSourceCompilation) все привязки соответствующим x:DataTypeобразом.

Чтобы убедиться, что эти предупреждения не игнорируются, рассмотрите возможность изменения конкретных предупреждений для создания ошибок со свойством $(WarningsAsErrors) сборки:

<WarningsAsErrors>$(WarningsAsErrors);XC0022;XC0023</WarningsAsErrors>

Чтобы игнорировать эти предупреждения, используйте $(NoWarn) свойство сборки с определенными кодами предупреждений:

<NoWarn>$(NoWarn);XC0022;XC0023</NoWarn>

Внимание

XC0022 и XC0023 предупреждения всегда будут подавляться, если $(MauiStrictXamlCompilation) для свойства сборки не задано значение true.

Если в файле проекта приложения задано $(TreatWarningsAsErrors) свойство true сборки, но вы хотите игнорировать определенные предупреждения компилятора XAML, используйте $(NoWarn) свойство сборки для молчания этих предупреждений или $(WarningsNotAsErrors) свойства сборки, чтобы уменьшить серьезность некоторых конкретных кодов.

По умолчанию .NET MAUI создает предупреждения сборки для привязок XAML, которые не используют скомпилированные привязки. Вы можете отказаться от скомпилированных привязок, которые обрабатываются как ошибки, задав $(MauiStrictXamlCompilation) свойства и $(TreatWarningsAsErrors) создайте свойства true в файле проекта приложения (*.csproj):

<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<MauiStrictXamlCompilation>true</MauiStrictXamlCompilation>

Примечание.

По умолчанию свойство сборки не публикуетсяfalse, $(MauiStrictXamlCompilation) если вы не публикуете приложение с помощью полной обрезки или NativeAOT.

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

Привязки, написанные в коде, обычно используют строковые пути, которые разрешаются во время выполнения с отражением. Однако метод 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}";

Кроме того, Binding.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), выполняется примерно в 5 раз быстрее, чем задание BindingContext для классических привязок.
  • Задание BindingContext для скомпилированных привязок, не использующих уведомление об изменении свойства (то есть для привязок в режиме OneTime), выполняется примерно в 7 раз быстрее, чем задание BindingContext для классических привязок.

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