Tagliare un'app MAUI .NET
Quando compila l'app, l'interfaccia utente dell'app multipiattaforma .NET (.NET MAUI) può usare un linker denominato ILLink per ridurre le dimensioni complessive dell'app con una tecnica nota come taglio. ILLink riduce le dimensioni analizzando il codice intermedio prodotto dal compilatore. Rimuove metodi, proprietà, campi, eventi, struct e classi inutilizzati per produrre un'app che contiene solo dipendenze di codice e assembly necessarie per eseguire l'app.
Per evitare modifiche nel comportamento quando si tagliano le app, .NET fornisce un'analisi statica della compatibilità del taglio tramite avvisi di taglio. Il trimmer genera avvisi di taglio quando trova codice che potrebbe non essere compatibile con il taglio. Se sono presenti avvisi di taglio, devono essere corretti e l'app deve essere testata accuratamente dopo il taglio per assicurarsi che non siano presenti modifiche di comportamento. Per altre informazioni, vedere Introduzione agli avvisi di taglio.
Comportamento di taglio
Il comportamento di taglio può essere controllato impostando la proprietà di $(TrimMode)
compilazione su partial
o full
su :
<PropertyGroup>
<TrimMode>full</TrimMode>
</PropertyGroup>
Importante
La $(TrimMode)
proprietà di compilazione non deve essere condizionata dalla configurazione di compilazione. Ciò è dovuto al fatto che le opzioni delle funzionalità sono abilitate o disabilitate in base al valore della $(TrimMode)
proprietà di compilazione e le stesse funzionalità devono essere abilitate o disabilitate in tutte le configurazioni di compilazione in modo che il codice si comporti in modo identico.
La full
modalità di taglio rimuove qualsiasi codice non usato dall'app. La partial
modalità di taglio taglia la libreria di classi di base (BCL), gli assembly per le piattaforme sottostanti (ad esempio Mono.Android.dll e Microsoft.iOS.dll) e tutti gli altri assembly che hanno scelto di tagliare con l'elemento $(TrimmableAsssembly)
di compilazione:
<ItemGroup>
<TrimmableAssembly Include="MyAssembly" />
</ItemGroup>
Equivale all'impostazione [AssemblyMetadata("IsTrimmable", "True")]
durante la compilazione dell'assembly.
Nota
Non è necessario impostare la $(PublishTrimmed)
proprietà di compilazione su true
nel file di progetto dell'app, perché questa proprietà è impostata per impostazione predefinita.
Per altre opzioni di taglio, vedi Opzioni di taglio.
Impostazioni predefinite di taglio
Per impostazione predefinita, le compilazioni di Android e Mac Catalyst usano il taglio parziale quando la configurazione della compilazione è impostata su una build di versione. iOS usa il taglio parziale per qualsiasi compilazione del dispositivo, indipendentemente dalla configurazione della compilazione e non usa il taglio per le compilazioni del simulatore.
Incompatibilità di taglio
Le seguenti funzionalità MAUI .NET non sono compatibili con il taglio completo e verranno rimosse dal trimmer:
- Espressioni di associazione in cui tale percorso di associazione è impostato su una stringa. Usare invece associazioni compilate. Per altre informazioni, vedere Binding compilati.
- Operatori di conversione impliciti, quando si assegna un valore di un tipo incompatibile a una proprietà in XAML o quando due proprietà di tipi diversi usano un data binding. È invece necessario definire un TypeConverter per il tipo e collegarlo al tipo usando .TypeConverterAttribute Per altre informazioni, vedere Definire un TypeConverter per sostituire un operatore di conversione implicita.
- Caricamento di XAML in fase di esecuzione con il metodo di LoadFromXaml estensione. Questo codice XAML può essere reso sicuro annotando tutti i tipi che possono essere caricati in fase di esecuzione con l'attributo
DynamicallyAccessedMembers
o l'attributoDynamicDependency
. Tuttavia, questo è molto soggetto a errori e non è consigliato. - Ricezione dei dati di navigazione tramite .QueryPropertyAttribute È invece necessario implementare l'interfaccia IQueryAttributable sui tipi che devono accettare parametri di query. Per altre informazioni, vedere Elaborare i dati di navigazione usando un singolo metodo.
- La proprietà
SearchHandler.DisplayMemberName
. È invece necessario fornire un oggetto ItemTemplate per definire l'aspetto dei SearchHandler risultati. Per altre informazioni, vedere Definire l'aspetto degli elementi dei risultati della ricerca. - Controllo HybridWebView , a causa dell'uso di funzionalità di serializzazione dinamica
System.Text.Json
. - Personalizzazione dell'interfaccia utente con l'estensione di markup XAML
OnPlatform
. È invece consigliabile usare la classe OnPlatform<T>. Per altre informazioni, vedere Personalizzare l'aspetto dell'interfaccia utente in base alla piattaforma. - Personalizzazione dell'interfaccia utente con l'estensione di markup XAML
OnIdiom
. È invece consigliabile usare la classe OnIdiom<T>. Per altre informazioni, vedere Personalizzare l'aspetto dell'interfaccia utente in base al linguaggio del dispositivo.
In alternativa, è possibile usare commutatori di funzionalità in modo che il trimmer mantenga il codice per queste funzionalità. Per altre informazioni, vedere Trimming feature switch.For more information, see Trimming feature switch.
Per le incompatibilità di taglio .NET, vedere Incompatibilità di taglio note.
Definire un TypeConverter per sostituire un operatore di conversione implicito
Non è possibile basarsi su operatori di conversione impliciti quando si assegna un valore di un tipo incompatibile a una proprietà in XAML o quando due proprietà di tipi diversi usano un data binding, quando è abilitato il taglio completo. Ciò è dovuto al fatto che i metodi dell'operatore implicito possono essere rimossi dal trimmer se non vengono usati nel codice C#. Per altre informazioni sugli operatori di conversione implicita, vedere Operatori di conversione espliciti e impliciti definiti dall'utente.
Si consideri ad esempio il tipo seguente che definisce gli operatori di conversione impliciti tra SizeRequest
e 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);
}
Con il taglio completo abilitato, gli operatori di conversione implicita tra SizeRequest
e Size
possono essere rimossi dal trimmer se non vengono usati nel codice C#.
È invece necessario definire un TypeConverter per il tipo e collegarlo al tipo usando :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();
}
}
}
Interruttori delle funzionalità di taglio
.NET MAUI include direttive trimmer, note come commutatori di funzionalità, che consentono di mantenere il codice per le funzionalità che non sono sicure. Queste direttive trimmer possono essere usate quando la proprietà di $(TrimMode)
compilazione è impostata su full
, nonché per Native AOT:
Proprietà MSBuild | Descrizione |
---|---|
MauiEnableVisualAssemblyScanning |
Se impostato su true , .NET MAUI analizzerà gli assembly per individuare i tipi che implementano IVisual e per [assembly:Visual(...)] gli attributi e registrerà questi tipi. Per impostazione predefinita, questa proprietà di compilazione è impostata su false quando è abilitato il taglio completo. |
MauiShellSearchResultsRendererDisplayMemberNameSupported |
Se impostato su false , il valore di SearchHandler.DisplayMemberName verrà ignorato. È invece necessario fornire un oggetto ItemTemplate per definire l'aspetto dei SearchHandler risultati. Per impostazione predefinita, questa proprietà di compilazione è impostata su false quando è abilitato il taglio completo o Native AOT. |
MauiQueryPropertyAttributeSupport |
Se impostato su false , [QueryProperty(...)] gli attributi non verranno usati per impostare i valori delle proprietà durante lo spostamento. È invece necessario implementare l'interfaccia IQueryAttributable per accettare i parametri di query. Per impostazione predefinita, questa proprietà di compilazione è impostata su false quando è abilitato il taglio completo o Native AOT. |
MauiImplicitCastOperatorsUsageViaReflectionSupport |
Se impostato su false , .NET MAUI non cercherà gli operatori di conversione impliciti durante la conversione di valori da un tipo a un altro. Ciò può influire sulle associazioni tra le proprietà con tipi diversi e l'impostazione di un valore della proprietà di un oggetto associabile con un valore di un tipo diverso. È invece necessario definire un TypeConverter oggetto per il tipo e collegarlo al tipo usando l'attributo TypeConverterAttribute . Per impostazione predefinita, questa proprietà di compilazione è impostata su false quando è abilitato il taglio completo o Native AOT. |
_MauiBindingInterceptorsSupport |
Se impostato su false , .NET MAUI non intercetta alcuna chiamata ai SetBinding metodi e non tenterà di compilarli. Per impostazione predefinita, questa proprietà di compilazione è impostata su true . |
MauiEnableXamlCBindingWithSourceCompilation |
Se impostato su true , .NET MAUI compilerà tutte le associazioni, incluse quelle in cui viene usata la Source proprietà . Se si abilita questa funzionalità, assicurarsi che tutte le associazioni siano corrette x:DataType in modo che vengano compilate o cancellate il tipo di dati con x:Data={x:Null}} se l'associazione non deve essere compilata. Per impostazione predefinita, questa proprietà di compilazione è impostata su true quando è abilitato il taglio completo o Native AOT. |
MauiHybridWebViewSupported |
Se impostato su false , il HybridWebView controllo non sarà disponibile. Per impostazione predefinita, questa proprietà di compilazione è impostata su false quando è abilitato il taglio completo o Native AOT. |
Queste proprietà di MSBuild hanno anche opzioni equivalenti AppContext :
- La
MauiEnableVisualAssemblyScanning
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled
. - La
MauiShellSearchResultsRendererDisplayMemberNameSupported
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported
. - La
MauiQueryPropertyAttributeSupport
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported
. - La
MauiImplicitCastOperatorsUsageViaReflectionSupport
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported
. - La
_MauiBindingInterceptorsSupport
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported
. - La
MauiEnableXamlCBindingWithSourceCompilation
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled
. - La
MauiHybridWebViewSupported
proprietà MSBuild ha un'opzione equivalente denominata AppContextMicrosoft.Maui.RuntimeFeature.IsHybridWebViewSupported
.
Il modo più semplice per usare un commutatore di funzionalità consiste nell'inserire la proprietà MSBuild corrispondente nel file di progetto dell'app (*.csproj), che causa il taglio del codice correlato dagli assembly MAUI .NET.
Mantenere il codice
Quando si usa il trimmer, talvolta rimuove il codice che potrebbe essere stato chiamato in modo dinamico, anche indirettamente. È possibile indicare al trimmer di mantenere i membri annotandoli con l'attributo DynamicDependency
. Questo attributo può essere usato per esprimere una dipendenza da un tipo e da un subset di membri o da membri specifici.
Importante
Ogni membro dell'elenco di controllo di gruppo che non può essere determinato in modo statico da usare dall'app è soggetto a essere rimosso.
L'attributo DynamicDependency
può essere applicato a costruttori, campi e metodi:
[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
helper.Invoke(null, null);
}
In questo esempio, garantisce DynamicDependency
che il Helper
metodo venga mantenuto. Senza l'attributo, il taglio rimuove Helper
MyAssembly
o rimuove MyAssembly
completamente se non viene fatto riferimento altrove.
L'attributo specifica il membro da mantenere tramite o string
tramite l'attributo DynamicallyAccessedMembers
. Il tipo e l'assembly sono impliciti nel contesto dell'attributo oppure specificati in modo esplicito nell'attributo (da Type
o da string
per il tipo e il nome dell'assembly).
Le stringhe di tipo e membro usano una variante del formato di stringa dell’ID commento della documentazione di C#, senza il prefisso del membro. La stringa del membro non deve includere il nome del tipo dichiarante e può omettere i parametri per mantenere tutti i membri del nome specificato. Gli esempi seguenti mostrano usi validi:
[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<>))]
Mantenere gli assembly
È possibile specificare assembly che devono essere esclusi dal processo di taglio, consentendo al contempo l'eliminazione di altri assembly. Questo approccio può essere utile quando non è possibile usare facilmente l'attributo DynamicDependency
o non controllare il codice da tagliare.
Quando taglia tutti gli assembly, è possibile indicare al trimmer di ignorare un assembly impostando un TrimmerRootAssembly
elemento MSBuild nel file di progetto:
<ItemGroup>
<TrimmerRootAssembly Include="MyAssembly" />
</ItemGroup>
Nota
L'estensione .dll
non è necessaria quando si imposta la TrimmerRootAssembly
proprietà MSBuild.
Se il trimmer ignora un assembly, viene considerato rooted, il che significa che vengono mantenute tutte le relative dipendenze comprensibili in modo statico. È possibile ignorare altri assembly aggiungendo altre TrimmerRootAssembly
proprietà di MSBuild a <ItemGroup>
.
Mantenere assembly, tipi e membri
È possibile passare il trimmer di un file di descrizione XML che specifica quali assembly, tipi e membri devono essere conservati.
Per escludere un membro dal processo di taglio quando si tagliano tutti gli assembly, impostare l'elemento TrimmerRootDescriptor
MSBuild nel file di progetto sul file XML che definisce i membri da escludere:
<ItemGroup>
<TrimmerRootDescriptor Include="MyRoots.xml" />
</ItemGroup>
Il file XML usa quindi il formato del descrittore trimmer per definire i membri da escludere:
<linker>
<assembly fullname="MyAssembly">
<type fullname="MyAssembly.MyClass">
<method name="DynamicallyAccessedMethod" />
</type>
</assembly>
</linker>
In questo esempio, il file XML specifica un metodo accessibile dinamicamente dall'app, escluso dal taglio.
Quando un assembly, un tipo o un membro è elencato nel codice XML, l'azione predefinita è l'archiviazione, il che significa che indipendentemente dal fatto che il trimmer pensi che venga usato o meno, viene mantenuto nell'output.
Nota
I tag di conservazione sono ambiguamente inclusivi. Se non fornisci il livello di dettaglio successivo, includerà tutti gli elementi figlio. Se un assembly è elencato senza alcun tipo, tutti i tipi e i membri dell'assembly verranno mantenuti.
Contrassegnare un assembly come ritaglio sicuro
Se si dispone di una libreria nel progetto o si è uno sviluppatore di una libreria riutilizzabile e si vuole che il trimmer consideri l'assembly come trimmable, è possibile contrassegnare l'assembly come trim safe aggiungendo la IsTrimmable
proprietà MSBuild al file di progetto per l'assembly:
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
Questo contrassegna l'assembly come "trimmable" e abilita gli avvisi di taglio per il progetto. Essere "trimmable" significa che l'assembly è considerato compatibile con il taglio e non deve avere avvisi di taglio quando viene compilato l'assembly. Se usato in un'app tagliata, i membri inutilizzati dell'assembly vengono rimossi nell'output finale.
Quando si usa la distribuzione AOT nativa in .NET 9+, impostando la proprietà IsAotCompatible
MSBuild su true
assegna anche un valore di true
alla proprietà IsTrimmable
e abilita proprietà di compilazione aggiuntive dell'analizzatore AOT. Per ulteriori informazioni sugli analizzatori di compatibilità AOT, vedere . Per ulteriori informazioni sulla distribuzione AOT nativa per .NET MAUI, vedere distribuzione AOT nativa.
L'impostazione della IsTrimmable
proprietà MSBuild su true
nel file di progetto inserisce l'attributo nell'assembly AssemblyMetadata
:
[assembly: AssemblyMetadata("IsTrimmable", "True")]
In alternativa, è possibile aggiungere l'attributo nell'assembly AssemblyMetadata
senza aver aggiunto la IsTrimmable
proprietà MSBuild al file di progetto per l'assembly.
Nota
Se la IsTrimmable
proprietà MSBuild è impostata per un assembly, viene eseguito l'override dell'attributo AssemblyMetadata("IsTrimmable", "True")
. Ciò consente di acconsentire esplicitamente a un assembly di taglio anche se non dispone dell'attributo o di disabilitare il taglio di un assembly con l'attributo .
Eliminare gli avvisi di analisi
Quando il trimmer è abilitato, rimuove IL che non è raggiungibile in modo statico. Le app che usano reflection o altri modelli che creano dipendenze dinamiche possono essere interrotte di conseguenza. Per avvisare di questi modelli, quando si contrassegna un assembly come tronco sicuro, gli autori della libreria devono impostare la SuppressTrimAnalysisWarnings
proprietà MSBuild su false
:
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
La mancata eliminazione degli avvisi di analisi di taglio includerà avvisi sull'intera app, tra cui codice personalizzato, codice della libreria e codice SDK.
Mostra avvisi dettagliati
L'analisi trim genera al massimo un avviso per ogni assembly proveniente da un PackageReference
oggetto , a indicare che gli elementi interni dell'assembly non sono compatibili con il taglio. In qualità di autore della libreria, quando si contrassegna un assembly come ritaglio sicuro, è necessario abilitare singoli avvisi per tutti gli assembly impostando la TrimmerSingleWarn
proprietà MSBuild su false
:
<PropertyGroup>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
Questa impostazione mostra tutti gli avvisi dettagliati, invece di compressione in un singolo avviso per assembly.