Freigeben über


Optimieren einer .NET MAUI-App

Wenn es Ihre App entwickelt, kann .NET Multi-Plattform App UI (.NET MAUI) einen als ILLink bekannten Linker verwenden, um die Gesamtgröße der App mit einer Technik zu verringern, die als Kürzen bekannt ist. ILLink reduziert die Größe, indem der vom Compiler erzeugte Zwischencode analysiert wird. Es entfernt nicht verwendete Methoden, Eigenschaften, Felder, Ereignisse, Strukturen und Klassen, um eine App zu erstellen, die nur Code und Assembly-Abhängigkeiten enthält, die für die Ausführung der App erforderlich sind.

Um Änderungen im Verhalten beim Trimmen von Apps zu verhindern, bietet .NET statische Analysen zur Trim-Kompatibilität durch Trim-Warnungen. Der Trimmer erzeugt Trim-Warnungen, wenn er Code findet, der möglicherweise beim Trim-Vorgang nicht kompatibel ist. Wenn es Warnungen zu Kürzen gibt, sollten sie behoben werden, und die App sollte nach dem Kürzen gründlich getestet werden, um sicherzustellen, dass keine Verhaltensänderungen vorhanden sind. Weitere Informationen finden Sie in der Einführung zum Kürzen von Warnungen.

Kürzungsverhalten

Das Kürzungsverhalten kann gesteuert werden, indem die $(TrimMode) Buildeigenschaft auf entweder partial oder full gesetzt wird.

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

Wichtig

Legen Sie die Buildeigenschaft nicht fest, wenn Sie die $(TrimMode) native AOT-Bereitstellung verwenden, da dies automatisch das vollständige Trimmen Ihrer App durchführt. Weitere Informationen finden Sie unter native AOT-Bereitstellung unter iOS und Mac Catalyst.

Der full Kürzungsmodus entfernt Code, der von Ihrer Anwendung nicht verwendet wird. Der partial Trimm-Modus trimmt die Basisklassenbibliothek (BCL), Assemblys für die zugrunde liegenden Plattformen (z. B. Mono.Android.dll und Microsoft.iOS.dll) und alle anderen Assemblys, die sich für das Trimmen mit dem $(TrimmableAsssembly) Build-Element entschieden haben:

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

Dies entspricht der Einstellung [AssemblyMetadata("IsTrimmable", "True")] beim Erstellen der Assembly.

Die vollständige Kürzung sollte nicht durch die Buildkonfiguration bedingt werden. Dies liegt daran, dass Featureoptionen basierend auf dem Wert der $(TrimMode) Buildeigenschaft aktiviert oder deaktiviert sind und dieselben Features in allen Buildkonfigurationen aktiviert oder deaktiviert werden sollten, damit sich Ihr Code identisch verhält.

Hinweis

Legen Sie die Build-Eigenschaft nicht in der Projektdatei Ihrer App fest, da diese bei Bedarf standardmäßig gesetzt wird.

Weitere Kürzungsoptionen finden Sie unter "Trimming"-Optionen.

Standardeinstellungen für das Kürzen

Standardmäßig wird bei Android- und Mac Catalyst-Builds partielles Trimmen verwendet, wenn die Buildkonfiguration auf einen Release-Build festgelegt ist. iOS verwendet partielles Kürzen für alle Gerätebuilds, unabhängig von der Buildkonfiguration und verwendet keine Kürzung für Simulatorbuilds.

Kürzen von Inkompatibilitäten

Die folgenden .NET MAUI-Features sind nicht mit vollständiger Code-Reduzierung kompatibel und werden vom Trimmer-Tool entfernt:

Alternativ können Sie Feature-Schalter verwenden, damit der Trimmer den Code für diese Features beibehält. Weitere Informationen finden Sie unter Trimmen von Funktionsschaltern.

Informationen zu .NET-Kürzungsinkompatibilitäten finden Sie unter "Bekannte Kürzungsinkompatibilitäten".

Definieren eines TypeConverter-Elements zum Ersetzen eines impliziten Konvertierungsoperators

Es ist nicht möglich, beim Zuweisen eines Werts eines inkompatiblen Typs zu einer Eigenschaft in XAML auf implizite Konvertierungsoperatoren zu vertrauen, oder wenn zwei Eigenschaften unterschiedlicher Typen eine Datenbindung verwenden, wenn die vollständige Kürzung aktiviert ist. Dies liegt daran, dass die impliziten Operatormethoden vom Trimmer entfernt werden können, wenn sie nicht im C#-Code verwendet werden. Weitere Informationen zu impliziten Konvertierungsoperatoren finden Sie unter Benutzerdefinierte explizite und implizite Konvertierungsoperatoren.

Betrachten Sie beispielsweise den folgenden Typ, der implizite Konvertierungsoperatoren zwischen SizeRequest und 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);
}

Wenn die vollständige Kürzung aktiviert ist, können die impliziten Konvertierungsoperatoren zwischen SizeRequest und Size vom Trimmer entfernt werden, wenn sie nicht in Ihrem C#-Code verwendet werden.

Stattdessen sollten Sie einen TypeConverter für Ihren Typ definieren und ihn mit dem Typ anfügen, indem Sie TypeConverterAttribute verwenden:

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();
        }
    }
}

Trimmen von Funktionsschaltern

.NET MAUI verfügt über Trimmerdirektiven, die als Feature-Schalter bezeichnet werden, die es ermöglichen, den Code für Features beizubehalten, die nicht trim-sicher sind. Diese Trimmerdirektiven können verwendet werden, wenn die $(TrimMode) Buildeigenschaft auf full festgelegt ist, sowie für native AOT.

MSBuild-Eigenschaft Beschreibung
MauiEnableVisualAssemblyScanning Wenn auf true gesetzt, durchsucht .NET MAUI Baugruppen nach Typen, die IVisual implementieren, und nach [assembly:Visual(...)]-Attributen und registriert diese Typen. Standardmäßig wird diese Buildeigenschaft auf false festgelegt, wenn die vollständige Kürzung aktiviert ist.
MauiShellSearchResultsRendererDisplayMemberNameSupported Wenn auf false gesetzt, wird der Wert von SearchHandler.DisplayMemberName ignoriert. Stattdessen sollten Sie ein ItemTemplate bereitstellen, um das Erscheinungsbild der SearchHandler-Ergebnisse zu definieren. Standardmäßig wird diese Buildeigenschaft auf false festgelegt, wenn das vollständige Trimmen oder Native AOT aktiviert ist.
MauiQueryPropertyAttributeSupport Wenn auf false gesetzt, werden [QueryProperty(...)]-Attribute nicht verwendet, um Eigenschaftswerte beim Navigieren festzulegen. Stattdessen sollten Sie die IQueryAttributable-Schnittstelle implementieren, um Abfrageparameter zu akzeptieren. Standardmäßig wird diese Build-Eigenschaft auf false festgelegt, wenn die vollständige Trimmung oder Native AOT aktiviert ist.
MauiImplicitCastOperatorsUsageViaReflectionSupport Wenn auf false gesetzt, sucht .NET MAUI beim Konvertieren von Werten von einem Typ in einen anderen nicht nach impliziten Konvertierungsoperatoren. Dies kann sich auf Bindungen zwischen Eigenschaften mit unterschiedlichen Typen auswirken und einen Eigenschaftswert eines bindbaren Objekts mit einem Wert eines anderen Typs festlegen. Stattdessen sollten Sie ein TypeConverter für Ihren Typ definieren und es mit dem TypeConverterAttribute-Attribut an den Typ anhängen. Standardmäßig wird diese Buildeigenschaft auf false festgelegt, wenn die vollständige Trimmung oder Native AOT aktiviert ist.
_MauiBindingInterceptorsSupport Wenn auf false gesetzt, fängt .NET MAUI keine Aufrufe der SetBinding-Methoden ab und versucht nicht, sie zu kompilieren. Standardmäßig ist diese Buildeigenschaft auf true festgelegt.
MauiEnableXamlCBindingWithSourceCompilation Bei Festlegung auf true, .NET MAUI kompiliert alle Bindungen, einschließlich derjenigen, in denen die Source Eigenschaft verwendet wird. Wenn Sie diese Funktion aktivieren, stellen Sie sicher, dass alle Bindungen korrekt mit x:DataType konfiguriert sind, damit sie kompiliert werden. Sollte die Bindung nicht kompiliert werden, löschen Sie den Datentyp mit x:Data={x:Null}}. Standardmäßig ist diese Buildeigenschaft auf true gesetzt, wenn vollständiges Trimmen oder Native AOT aktiviert ist.
MauiHybridWebViewSupported Bei Festlegung auf false ist das Steuerelement HybridWebView nicht verfügbar. Standardmäßig wird diese Buildeigenschaft auf false festgelegt, wenn vollständiges Trimmen oder native AOT aktiviert ist.

Diese MSBuild-Eigenschaften verfügen auch über entsprechende AppContext Schalter:

  • Die MauiEnableVisualAssemblyScanning MSBuild-Eigenschaft verfügt über einen entsprechenden AppContext Switch mit dem Namen Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled.
  • Die MauiShellSearchResultsRendererDisplayMemberNameSupported MSBuild-Eigenschaft verfügt über einen entsprechenden AppContext Schalter namens Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported.
  • Die MauiQueryPropertyAttributeSupport MSBuild-Eigenschaft verfügt über einen gleichwertigen AppContext Schalter mit dem Namen Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported.
  • Die MauiImplicitCastOperatorsUsageViaReflectionSupport MSBuild-Eigenschaft verfügt über einen entsprechenden AppContext Schalter mit dem Namen Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported.
  • Die _MauiBindingInterceptorsSupport MSBuild-Eigenschaft verfügt über einen entsprechenden AppContext Schalter mit dem Namen Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported.
  • Die MauiEnableXamlCBindingWithSourceCompilation MSBuild-Eigenschaft hat einen entsprechenden AppContext Schalter namens Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled.
  • Die MauiHybridWebViewSupported MSBuild-Eigenschaft verfügt über einen entsprechenden AppContext Schalter mit dem Namen Microsoft.Maui.RuntimeFeature.IsHybridWebViewSupported.

Am einfachsten können Sie einen Featureswitch nutzen, indem Sie die entsprechende MSBuild-Eigenschaft in die Projektdatei Ihrer App (*.csproj) einfügen, wodurch der zugehörige Code von den .NET MAUI-Assemblys gekürzt wird.

Code beibehalten

Wenn Sie den Trimmer verwenden, entfernt es manchmal Code, den Sie möglicherweise dynamisch aufgerufen haben, sogar indirekt. Sie können den Trimmer anweisen, Mitglieder beizubehalten, indem Sie sie mit dem DynamicDependency-Attribut annotieren. Dieses Attribut kann verwendet werden, um eine Abhängigkeit entweder von einem Typ und einer Teilmenge von Mitgliedern oder von bestimmten Mitgliedern auszudrücken.

Wichtig

Jedes Mitglied der BCL, bei dem nicht statisch festgestellt werden kann, dass es von der App verwendet wird, muss entfernt werden.

Das Attribut DynamicDependency kann auf Konstruktoren, Felder und Methoden angewendet werden:

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

In diesem Beispiel sorgt das DynamicDependency dafür, dass die Methode Helper beibehalten wird. Ohne das Attribut würde das Kürzen Helper aus MyAssembly entfernen oder MyAssembly vollständig entfernen, wenn es nicht an anderer Stelle referenziert wird.

Das Attribut gibt das zu behaltende Mitglied über ein string oder über das DynamicallyAccessedMembers-Attribut an. Der Typ und die Assembly sind entweder implizit im Attributkontext oder explizit im Attribut angegeben (durch Type oder durch strings für den Typ und den Assemblynamen).

Die Typ- und Member-Zeichenfolgen verwenden eine Variante des C#-Dokumentationskommentar-ID-Zeichenfolgenformats, jedoch ohne das Member-Präfix. Die Zeichenfolge für das Mitglied sollte den Namen des deklarierenden Typs nicht enthalten und kann Parameter auslassen, um alle Mitglieder mit dem angegebenen Namen beizubehalten. Die folgenden Beispiele zeigen gültige Verwendungen:

[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<>))]

Beibehalten von Assemblys

Es ist möglich, Assemblys anzugeben, die vom Kürzungsprozess ausgeschlossen werden sollten, während andere Assemblys gekürzt werden können. Dieser Ansatz kann nützlich sein, wenn Sie das DynamicDependency Attribut nicht einfach verwenden können, oder den Code, der gekürzt wird, nicht steuern.

Wenn alle Assemblys gekürzt werden, können Sie den Trimmer anweisen, eine Assembly zu überspringen, indem Sie ein TrimmerRootAssembly MSBuild-Element in der Projektdatei festlegen:

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

Hinweis

Die Erweiterung .dll ist nicht erforderlich, wenn die Eigenschaft TrimmerRootAssembly MSBuild eingestellt wird.

Wenn der Trimmer eine Assembly überspringt, wird sie als gewurzelt betrachtet, was bedeutet, dass sie und alle ihre statisch verstandenen Abhängigkeiten beibehalten werden. Sie können zusätzliche Assemblies überspringen, indem Sie weitere TrimmerRootAssembly MSBuild-Eigenschaften zu <ItemGroup> hinzufügen.

Beibehalten von Assemblierungen, Typen und Elementen

Sie können den Trimmer mit einer XML-Beschreibungsdatei versehen, die angibt, welche Assemblys, Typen und Mitglieder aufbewahrt werden müssen.

Um ein Mitglied beim Kürzen aller Assemblys von der Verarbeitung auszuschließen, legen Sie das TrimmerRootDescriptor MSBuild-Element in der Projektdatei auf die XML-Datei fest, die die auszuschließenden Mitglieder definiert.

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

Die XML-Datei verwendet dann das Trimmerdeskriptorformat, um zu definieren, welche Member ausgeschlossen werden sollen:

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

In diesem Beispiel gibt die XML-Datei eine Methode an, auf die dynamisch von der App zugegriffen wird, die von der Kürzung ausgeschlossen wird.

Wenn eine Assembly, ein Typ oder ein Mitglied im XML-Code aufgeführt ist, ist die Standardaktion die Beibehaltung. Dies bedeutet, dass unabhängig davon, ob der Trimmer denkt, dass es verwendet wird oder nicht, es in der Ausgabe erhalten bleibt.

Hinweis

Die Tags zur Erhaltung sind mehrdeutig und allgemein gehalten. Wenn Sie nicht die nächste Detailebene angeben, werden alle untergeordneten Elemente eingeschlossen. Wenn eine Assembly ohne jegliche Typen aufgeführt ist, werden alle Typen und Mitglieder der Assembly beibehalten.

Eine Assembly als trimsicher kennzeichnen

Wenn Sie über eine Bibliothek in Ihrem Projekt verfügen oder Entwickler einer wiederverwendbaren Bibliothek sind und möchten, dass der Trimmer Ihre Assembly als ausschnittbar behandelt, können Sie die Assembly als trimmsicher markieren, indem Sie die IsTrimmable MSBuild-Eigenschaft zur Projektdatei Ihrer Assembly hinzufügen.

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

Dadurch wird Ihre Assembly als „kürzbar“ gekennzeichnet, und Kürzungswarnungen werden für dieses Projekt aktiviert. „Kürzbar“ bedeutet, dass Ihre Bibliothek als mit Kürzungen kompatibel gilt und beim Erstellen der Bibliothek keine Kürzungswarnungen enthalten sollte. Wenn die Assembly in einer gekürzten App verwendet wird, werden die nicht genutzten Member in der finalen Ausgabe entfernt.

Wenn Sie die native AOT-Bereitstellung in .NET 9+ verwenden, weist das Festlegen der IsAotCompatible MSBuild-Eigenschaft auf true auch der true-Eigenschaft einen Wert von IsTrimmable zu und aktiviert zusätzliche AOT-Analyseeigenschaften für den Build. Weitere Informationen zu AOT-Analyzern finden Sie unter AOT-Kompatibilitätsanalysatoren. Weitere Informationen zur nativen AOT-Bereitstellung für .NET MAUI finden Sie unter Native AOT-Bereitstellung.

Wenn Sie die MSBuild-Eigenschaft IsTrimmable in Ihrer Projektdatei auf true setzen, wird das Attribut AssemblyMetadata in Ihre Assembly eingefügt:

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

Alternativ können Sie das AssemblyMetadata-Attribut in Ihre Assembly einfügen, ohne die IsTrimmable MSBuild-Eigenschaft zur Projektdatei für Ihre Baugruppe hinzugefügt zu haben.

Hinweis

Wenn die IsTrimmable-MSBuild-Eigenschaft für eine Assembly festgelegt ist, überschreibt dies das AssemblyMetadata("IsTrimmable", "True")-Attribut. Damit können Sie eine Baugruppe für das Trimmen auswählen, auch wenn sie das Attribut nicht hat, oder das Trimmen einer Baugruppe, die das Attribut hat, deaktivieren.

Unterdrücken von Analysewarnungen

Wenn der Trimmer aktiviert ist, entfernt er das IL, das nicht statisch erreichbar ist. Apps, die Reflection oder andere Muster verwenden, die dynamische Abhängigkeiten erzeugen, können infolgedessen funktionsunfähig werden. Um vor solchen Mustern zu warnen, sollten Bibliotheksautoren beim Markieren einer Assembly als trimsicher die SuppressTrimAnalysisWarnings MSBuild-Eigenschaft auf false festlegen.

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

Das Nicht-Unterdrücken von Warnungen zur Analyse des Abschneidens schließt Warnungen zur gesamten App ein, einschließlich Ihres eigenen Codes, des Bibliothekscodes und des SDK-Codes.

Anzeigen ausführlicher Warnungen

Die Trimm-Analyse gibt höchstens eine Warnung für jedes Assembly aus, das aus einem PackageReference stammt, was darauf hinweist, dass die internen Strukturen des Assemblys nicht mit dem Trimmen kompatibel sind. Als Bibliotheksautor sollten Sie, wenn Sie eine Assembly als Trim sicher kennzeichnen, einzelne Warnungen für alle Assemblies aktivieren, indem Sie die TrimmerSingleWarn MSBuild-Eigenschaft auf false setzen.

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

Diese Einstellung zeigt alle detaillierten Warnungen an, anstatt sie zu einer einzigen Warnung pro Assembly zusammenzufassen.

Siehe auch