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


Обрезка приложения .NET MAUI

При создании приложения пользовательский интерфейс многоплатформенного приложения .NET (.NET MAUI) может использовать компоновщик, вызывающийся ILLink для уменьшения общего размера приложения с помощью метода, известного как обрезка. ILLink уменьшает размер, анализируя промежуточный код, созданный компилятором. Он удаляет неиспользуемые методы, свойства, поля, события, структуры и классы для создания приложения, содержащего только зависимости кода и сборки, необходимые для запуска приложения.

Чтобы предотвратить изменения в поведении при обрезке приложений, .NET предоставляет статический анализ совместимости обрезки с помощью предупреждений обрезки. Триммер выдает предупреждения об обрезки при поиске кода, который может быть несовместим с обрезкой. Если должны быть исправлены предупреждения об обрезки, и приложение должно быть тщательно проверено после обрезки, чтобы убедиться, что изменения поведения отсутствуют. Дополнительные сведения см. в разделе "Общие сведения об обрезке предупреждений".

Поведение обрезки

Поведение обрезки можно контролировать, задав для свойства сборки $(TrimMode) значение partial или full:

<PropertyGroup>
  <TrimMode>full</TrimMode>
</PropertyGroup>

Внимание

Свойство $(TrimMode) сборки не должно быть обусловлено конфигурацией сборки. Это связано с тем, что параметры включены или отключены на основе значения $(TrimMode) свойства сборки, а одни и те же функции должны быть включены или отключены во всех конфигурациях сборки, чтобы код вел себя одинаково.

Режим full обрезки удаляет любой код, который не используется приложением. Режим partial обрезки обрезает библиотеку базовых классов (BCL), сборки для базовых платформ (например , Mono.Android.dll и Microsoft.iOS.dll), а также любые другие сборки, которые решили обрезать элемент $(TrimmableAsssembly) сборки:

<ItemGroup>
  <TrimmableAssembly Include="MyAssembly" />
</ItemGroup>

Это эквивалентно настройке [AssemblyMetadata("IsTrimmable", "True")] при сборке сборки.

Примечание.

Не обязательно задать $(PublishTrimmed) свойство true сборки в файле проекта приложения, так как это задано по умолчанию.

Дополнительные параметры обрезки см. в разделе "Параметры обрезки".

Обрезка по умолчанию

По умолчанию сборки Android и Mac Catalyst используют частичные обрезки при установке конфигурации сборки выпуска. IOS использует частичное обрезка для любых сборок устройств независимо от конфигурации сборки и не использует обрезку для сборок симулятора.

Обрезка несовместимости

Следующие функции .NET MAUI несовместимы с полной обрезкой и будут удалены триммером:

  • Выражения привязки, в которых этот путь привязки имеет значение строки. Вместо этого используйте скомпилированные привязки. Дополнительные сведения см. в разделе "Скомпилированные привязки".
  • Операторы неявного преобразования, при назначении значения несовместимого типа свойству в XAML или при использовании привязки данных двумя свойствами разных типов. Вместо этого необходимо определить TypeConverter тип и присоединить его к типу с помощью .TypeConverterAttribute Дополнительные сведения см. в разделе "Определение typeConverter" для замены неявного оператора преобразования.
  • Загрузка XAML во время выполнения с LoadFromXaml помощью метода расширения. Этот КОД XAML можно безопасно обрезать, заключив все типы, которые можно загрузить во время выполнения с DynamicallyAccessedMembers помощью атрибута или атрибута DynamicDependency . Однако это очень подвержено ошибкам и не рекомендуется.
  • Получение данных навигации QueryPropertyAttributeс помощью . Вместо этого следует реализовать IQueryAttributable интерфейс для типов, которые должны принимать параметры запроса. Дополнительные сведения об обработке данных навигации с помощью одного метода см. в этом разделе.
  • Свойство SearchHandler.DisplayMemberName. Вместо этого необходимо указать внешний ItemTemplate SearchHandler вид результатов. Дополнительные сведения см. в разделе "Определение внешнего вида элемента результатов поиска".
  • Элемент HybridWebView управления из-за использования функций динамической System.Text.Json сериализации.

Кроме того, можно использовать переключатели функций, чтобы триммер сохранял код для этих функций. Дополнительные сведения см. в разделе "Параметры обрезки".

Сведения о несовместимости при обрезке .NET см. в разделе Известные несовместимости обрезки.

Определение typeConverter для замены неявного оператора преобразования

Невозможно полагаться на неявные операторы преобразования при назначении значения несовместимого типа свойству в XAML или при включении полной обрезки двух свойств разных типов. Это связано с тем, что неявные методы оператора могут быть удалены триммером, если они не используются в коде C#. Дополнительные сведения о неявных операторах преобразования см. в разделе Определяемые пользователем явные и неявные операторы преобразования.

Например, рассмотрим следующий тип, определяющий неявные операторы преобразования между SizeRequest и Size:

namespace MyMauiApp;

public struct SizeRequest : IEquatable<SizeRequest>
{
    public Size Request { get; set; }
    public Size Minimum { get; set; }

    public SizeRequest(Size request, Size minimum)
    {
        Request = request;
        Minimum = minimum;
    }

    public SizeRequest(Size request)
    {
        Request = request;
        Minimum = request;
    }

    public override string ToString()
    {
        return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum);
    }

    public bool Equals(SizeRequest other) => Request.Equals(other.Request) && Minimum.Equals(other.Minimum);

    public static implicit operator SizeRequest(Size size) => new SizeRequest(size);
    public static implicit operator Size(SizeRequest size) => size.Request;
    public override bool Equals(object? obj) => obj is SizeRequest other && Equals(other);
    public override int GetHashCode() => Request.GetHashCode() ^ Minimum.GetHashCode();
    public static bool operator ==(SizeRequest left, SizeRequest right) => left.Equals(right);
    public static bool operator !=(SizeRequest left, SizeRequest right) => !(left == right);
}

С включенной полной обрезкой операторы неявного преобразования между SizeRequest и Size могут быть удалены триммером, если они не используются в коде C#.

Вместо этого необходимо определить TypeConverter тип и присоединить его к типу с помощью TypeConverterAttribute:

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace MyMauiApp;

[TypeConverter(typeof(SizeRequestTypeConverter))]
public struct SizeRequest : IEquatable<SizeRequest>
{
    public Size Request { get; set; }
    public Size Minimum { get; set; }

    public SizeRequest(Size request, Size minimum)
    {
        Request = request;
        Minimum = minimum;
    }

    public SizeRequest(Size request)
    {
        Request = request;
        Minimum = request;
    }

    public override string ToString()
    {
        return string.Format("{{Request={0} Minimum={1}}}", Request, Minimum);
    }

    public bool Equals(SizeRequest other) => Request.Equals(other.Request) && Minimum.Equals(other.Minimum);

    public static implicit operator SizeRequest(Size size) => new SizeRequest(size);
    public static implicit operator Size(SizeRequest size) => size.Request;
    public override bool Equals(object? obj) => obj is SizeRequest other && Equals(other);
    public override int GetHashCode() => Request.GetHashCode() ^ Minimum.GetHashCode();
    public static bool operator ==(SizeRequest left, SizeRequest right) => left.Equals(right);
    public static bool operator !=(SizeRequest left, SizeRequest right) => !(left == right);

    private sealed class SizeRequestTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
            => sourceType == typeof(Size);

        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
            => value switch
            {
                Size size => (SizeRequest)size,
                _ => throw new NotSupportedException()
            };

        public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
            => destinationType == typeof(Size);

        public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
        {
            if (value is SizeRequest sizeRequest)
            {
                if (destinationType == typeof(Size))
                    return (Size)sizeRequest;
            }
            throw new NotSupportedException();
        }
    }
}

Обрезка переключателей функций

В .NET MAUI есть директивы триммера, известные как коммутаторы функций, которые позволяют сохранить код для функций, которые не являются безопасными. Эти директивы триммера можно использовать, если $(TrimMode) для свойства сборки задано значение full, а также для собственного AOT:

Свойство MSBuild Description
MauiEnableVisualAssemblyScanning Если задано значение true, .NET MAUI сканирует сборки для типов, реализующих IVisual и для [assembly:Visual(...)] атрибутов, и будет регистрировать эти типы. По умолчанию это свойство сборки имеет false значение, если включена полная обрезка.
MauiShellSearchResultsRendererDisplayMemberNameSupported Если задано значение false, значение SearchHandler.DisplayMemberName будет игнорироваться. Вместо этого необходимо указать внешний ItemTemplate SearchHandler вид результатов. По умолчанию это свойство сборки имеет false значение, если включена полная обрезка или собственный AOT.
MauiQueryPropertyAttributeSupport Если задано значение false, [QueryProperty(...)] атрибуты не будут использоваться для задания значений свойств при переходе. Вместо этого следует реализовать IQueryAttributable интерфейс для принятия параметров запроса. По умолчанию это свойство сборки имеет false значение, если включена полная обрезка или собственный AOT.
MauiImplicitCastOperatorsUsageViaReflectionSupport Если задано значение false, .NET MAUI не будет искать неявные операторы преобразования при преобразовании значений из одного типа в другой. Это может повлиять на привязки между свойствами с разными типами и задать значение свойства привязываемого объекта со значением другого типа. Вместо этого необходимо определить TypeConverter тип и присоединить его к типу с помощью атрибута TypeConverterAttribute . По умолчанию это свойство сборки имеет false значение, если включена полная обрезка или собственный AOT.
_MauiBindingInterceptorsSupport Если задано значение false, .NET MAUI не перехватывает вызовы SetBinding методов и не пытается компилировать их. По умолчанию для этого свойства сборки задано trueзначение .
MauiEnableXamlCBindingWithSourceCompilation Если задано значение true, .NET MAUI компилирует все привязки, включая те, где Source используется свойство. Если включить эту функцию, убедитесь, что все привязки имеют правильную версию x:DataType , чтобы они компилировались, или очистить тип данных, если x:Data={x:Null}} привязка не должна быть скомпилирована. По умолчанию это свойство сборки имеет true значение, если включена полная обрезка или собственный AOT.
MauiHybridWebViewSupported Если задано значение false, HybridWebView элемент управления не будет доступен. По умолчанию это свойство сборки имеет false значение, если включена полная обрезка или собственный AOT.

Эти свойства MSBuild также имеют эквивалентные AppContext коммутаторы:

  • Свойство MauiEnableVisualAssemblyScanning MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled.
  • Свойство MauiShellSearchResultsRendererDisplayMemberNameSupported MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported.
  • Свойство MauiQueryPropertyAttributeSupport MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported.
  • Свойство MauiImplicitCastOperatorsUsageViaReflectionSupport MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported.
  • Свойство _MauiBindingInterceptorsSupport MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported.
  • Свойство MauiEnableXamlCBindingWithSourceCompilation MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled.
  • Свойство MauiHybridWebViewSupported MSBuild имеет эквивалентный AppContext параметр с именем Microsoft.Maui.RuntimeFeature.IsHybridWebViewSupported.

Самый простой способ использовать переключатель функций — поместить соответствующее свойство MSBuild в файл проекта приложения (*.csproj), что приводит к обрезке связанного кода из сборок .NET MAUI.

Сохранение кода

При использовании триммера иногда удаляется код, который может вызываться динамически, даже косвенно. Вы можете указать триммеру сохранить элементы, заметив их атрибутом DynamicDependency . Этот атрибут можно использовать для выражения зависимости от типа и подмножества элементов или определенных элементов.

Внимание

Каждый член в BCL, который не может быть статически определен для использования приложением, подлежит удалению.

Атрибут DynamicDependency может применяться к конструкторам, полям и методам:

[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
    var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
    helper.Invoke(null, null);
}

В этом примере гарантируется, DynamicDependency что Helper метод хранится. Без атрибута обрезка будет удалена MyAssembly или удалена MyAssembly Helper полностью, если она не ссылается в другом месте.

Атрибут указывает член, который будет храниться через string атрибут или через нее DynamicallyAccessedMembers . Тип и сборка либо являются неявными в контексте атрибута, либо явно указаны в атрибуте (с помощью Type или string для типа и имени сборки).

В строках типа и элемента используется разновидность формата строки идентификатора комментария документации C# без префикса элемента. Строка-член не должна содержать имя декларационного типа и может опустить параметры, чтобы сохранить все члены указанного имени. В следующих примерах показаны допустимые варианты использования:

[DynamicDependency("Method()")]
[DynamicDependency("Method(System,Boolean,System.String)")]
[DynamicDependency("MethodOnDifferentType()", typeof(ContainingType))]
[DynamicDependency("MemberName")]
[DynamicDependency("MemberOnUnreferencedAssembly", "ContainingType", "UnreferencedAssembly")]
[DynamicDependency("MemberName", "Namespace.ContainingType.NestedType", "Assembly")]
// generics
[DynamicDependency("GenericMethodName``1")]
[DynamicDependency("GenericMethod``2(``0,``1)")]
[DynamicDependency("MethodWithGenericParameterTypes(System.Collections.Generic.List{System.String})")]
[DynamicDependency("MethodOnGenericType(`0)", "GenericType`1", "UnreferencedAssembly")]
[DynamicDependency("MethodOnGenericType(`0)", typeof(GenericType<>))]

Сохранение сборок

Можно указать сборки, которые следует исключить из процесса обрезки, позволяя обрезать другие сборки. Этот подход может быть полезным, если вы не можете легко использовать DynamicDependency атрибут или не контролирует код, который выполняется обрезки.

При обрезке всех сборок можно сообщить триммеру пропустить сборку, задав TrimmerRootAssembly элемент MSBuild в файле проекта:

<ItemGroup>
  <TrimmerRootAssembly Include="MyAssembly" />
</ItemGroup>

Примечание.

Расширение .dll не требуется при настройке TrimmerRootAssembly свойства MSBuild.

Если триммер пропускает сборку, она считается корневой, что означает, что она и все его статически понятные зависимости сохраняются. Можно пропустить дополнительные сборки, добавив в него <ItemGroup>дополнительные TrimmerRootAssembly свойства MSBuild.

Сохранение сборок, типов и элементов

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

Чтобы исключить элемент из процесса обрезки при обрезке всех сборок, задайте TrimmerRootDescriptor элемент MSBuild в файле проекта XML-файлу, который определяет элементы, которые необходимо исключить:

<ItemGroup>
  <TrimmerRootDescriptor Include="MyRoots.xml" />
</ItemGroup>

Затем XML-файл использует формат дескриптора триммера, чтобы определить, какие элементы следует исключить:

<linker>
  <assembly fullname="MyAssembly">
    <type fullname="MyAssembly.MyClass">
      <method name="DynamicallyAccessedMethod" />
    </type>
  </assembly>
</linker>

В этом примере XML-файл указывает метод, который динамически обращается к приложению, который исключается из обрезки.

Если сборка, тип или член перечислены в XML, действие по умолчанию сохраняется, что независимо от того, считает ли триммер, он используется или нет, он сохраняется в выходных данных.

Примечание.

Теги сохранения неоднозначно инклюзивно включены. Если вы не предоставляете следующий уровень детализации, он будет включать всех дочерних элементов. Если сборка указана без каких-либо типов, все типы и элементы сборки будут сохранены.

Пометить сборку как безопасную обрезку

Если у вас есть библиотека в проекте или вы являетесь разработчиком повторно используемых библиотек и хотите, чтобы триммер обрабатывал сборку как обрезную, можно пометить сборку как безопасную, добавив IsTrimmable свойство MSBuild в файл проекта для сборки:

<PropertyGroup>
    <IsTrimmable>true</IsTrimmable>
</PropertyGroup>

Это помечает сборку как "обрезаемую" и включает предупреждения обрезки для этого проекта. Будучи "обрезаемым" означает, что сборка считается совместимой с обрезкой и не должна иметь предупреждений обрезки при сборке сборки. При использовании в обрезаном приложении неиспользуемые элементы сборки удаляются в окончательных выходных данных.

IsTrimmable Задание свойства true MSBuild в файле проекта вставляет атрибут в сборкуAssemblyMetadata:

[assembly: AssemblyMetadata("IsTrimmable", "True")]

Кроме того, атрибут можно добавить AssemblyMetadata в сборку без добавления IsTrimmable свойства MSBuild в файл проекта для сборки.

Примечание.

IsTrimmable Если для сборки задано свойство MSBuild, это переопределяет AssemblyMetadata("IsTrimmable", "True") атрибут. Это позволяет выбрать сборку в обрезку, даже если у него нет атрибута или отключить обрезку сборки с атрибутом.

Подавление предупреждений для анализа

Если триммер включен, он удаляет IL, который недоступен статически. Приложения, использующие отражение или другие шаблоны, которые создают динамические зависимости, могут быть нарушены в результате. Чтобы предупредить об таких шаблонах, при маркировке сборки как безопасной для обрезки авторы библиотеки должны задать SuppressTrimAnalysisWarnings для свойства MSBuild значение false:

<PropertyGroup>
  <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

Не подавляя предупреждения об анализе обрезки, будут содержать предупреждения обо всем приложении, включая собственный код, код библиотеки и код пакета SDK.

Отображение подробных предупреждений

Анализ обрезки создает не более одного предупреждения для каждой сборки, полученной из нее PackageReference, указывая, что внутренние элементы сборки несовместимы с обрезкой. Как автор библиотеки, когда вы помечаете сборку как безопасную обрезку, следует включить отдельные предупреждения для всех сборок, задав TrimmerSingleWarn для свойства MSBuild значение false:

<PropertyGroup>
  <TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>

В этом параметре отображаются все подробные предупреждения, а не сворачивание их на одну сборку.

См. также