Compartir a través de


Recorte de una aplicación MAUI de .NET

Cuando compila la aplicación, .NET Multi-platform App UI (.NET MAUI) puede usar un enlazador llamado ILLink para reducir el tamaño general de la aplicación con una técnica conocida como recorte. ILLink reduce el tamaño mediante el análisis del código intermedio generado por el compilador. Elimina los métodos, propiedades, campos, eventos, estructuras y clases que no se usan para crear una aplicación que solo contenga el código y las dependencias de ensamblaje necesarios para ejecutarla.

Para evitar cambios en el comportamiento al recortar aplicaciones, .NET proporciona un análisis estático de la compatibilidad de recorte a través de advertencias de recorte. El recortador genera advertencias de recorte cuando encuentra código que puede no ser compatible con el recorte. Si hay advertencias de recorte que deben corregirse y la aplicación debe probarse exhaustivamente después del recorte para asegurarse de que no hay ningún cambio de comportamiento. Para obtener más información, vea Introducción a las advertencias de recorte.

Comportamiento de recorte

El comportamiento de recorte se puede controlar estableciendo la $(TrimMode) propiedad de compilación en partial o fullen :

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

Importante

No establezca la propiedad de compilación $(TrimMode) al usar la implementación de AOT nativa, que realiza automáticamente el recorte completo de la aplicación. Para obtener más información, consulte implementación nativa de AOT en iOS y Mac Catalyst.

El full modo de recorte quita cualquier código que no use la aplicación. El partial modo de recorte recorta la biblioteca de clases base (BCL), ensamblados para las plataformas subyacentes (como Mono.Android.dll y Microsoft.iOS.dll) y cualquier otro ensamblado que haya optado por recortar con el $(TrimmableAsssembly) elemento de compilación:

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

Esto equivale a establecer [AssemblyMetadata("IsTrimmable", "True")] al compilar el ensamblado.

El recorte completo no debe estar condicionado por la configuración de compilación. Esto se debe a que los modificadores de características están habilitados o deshabilitados en función del valor de la $(TrimMode) propiedad de compilación, y las mismas características deben habilitarse o deshabilitarse en todas las configuraciones de compilación para que el código se comporte de forma idéntica.

Nota:

No establezcas la propiedad de compilación $(PublishTrimmed) en el archivo de proyecto de la aplicación, ya que esto se establece de forma predeterminada cuando sea necesario.

Para obtener más opciones de recorte, consulte Opciones de recorte.

Recorte de los valores predeterminados

De forma predeterminada, las compilaciones android y Mac Catalyst usan recorte parcial cuando la configuración de compilación se establece en una compilación de versión. iOS usa el recorte parcial para las compilaciones de dispositivos, independientemente de la configuración de compilación, y no usa el recorte para las compilaciones del simulador.

Incompatibilidades de recorte

Las siguientes características de .NET MAUI no son compatibles con el recorte completo y el recortador lo quitará:

Como alternativa, puede usar modificadores de características para que el optimizador conserve el código de estas características. Para obtener más información, consulte Trimming feature switches (Recorte de modificadores de características).

Para conocer las incompatibilidades de recorte de .NET, consulte Incompatibilidades de recorte conocidas.

Definir un TypeConverter para reemplazar un operador de conversión implícito

No es posible confiar en operadores de conversión implícitos al asignar un valor de un tipo incompatible a una propiedad en XAML, o cuando dos propiedades de tipos diferentes usan un enlace de datos cuando se habilita el recorte completo. Esto se debe a que el optimizador podría quitar los métodos de operador implícitos si no se usan en el código de C#. Para obtener más información sobre los operadores de conversión implícitos, consulte Operadores de conversión explícitos e implícitos definidos por el usuario.

Por ejemplo, considere el siguiente tipo que define operadores de conversión implícitos entre SizeRequest y 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 el recorte completo habilitado, el optimizador podría quitar los operadores de conversión implícitos entre SizeRequest y Size si no se usan en el código de C#.

En su lugar, debe definir un TypeConverter para el tipo y adjuntarlo al tipo mediante :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();
        }
    }
}

Recorte de modificadores de características

.NET MAUI tiene directivas de optimizador, conocidas como modificadores de características, que permiten conservar el código de las características que no son seguras. Estas directivas de optimizador se pueden usar cuando la $(TrimMode) propiedad build está establecida fullen , así como para AOT nativo:

Propiedad de MSBuild Descripción
MauiEnableVisualAssemblyScanning Cuando se establece en true, .NET MAUI examinará los ensamblados para los tipos que implementan IVisual y para los atributos [assembly:Visual(...)], y registrará estos tipos. De forma predeterminada, esta propiedad de compilación se establece en false cuando se habilita el recorte completo.
MauiShellSearchResultsRendererDisplayMemberNameSupported Cuando se establece en false, se ignorará el valor de SearchHandler.DisplayMemberName. En su lugar, debes proporcionar un ItemTemplate para definir la apariencia de los resultados SearchHandler. De forma predeterminada, esta propiedad de compilación se establece en false cuando se habilita el recorte completo o el AOT nativo.
MauiQueryPropertyAttributeSupport Cuando se establece en false, los atributos [QueryProperty(...)] no se usarán para establecer valores de propiedad al navegar. En su lugar, debes implementar la interfaz IQueryAttributable para aceptar parámetros de consulta. De forma predeterminada, esta propiedad de compilación se establece en false cuando se habilita el recorte completo o el AOT nativo.
MauiImplicitCastOperatorsUsageViaReflectionSupport Cuando se establece en false, .NET MAUI no buscará operadores de conversión implícitos al convertir valores de un tipo a otro. Esto puede afectar a los enlaces entre propiedades con distintos tipos y establecer un valor de propiedad de un objeto enlazable con un valor de un tipo diferente. En su lugar, debes definir un TypeConverter para el tipo y adjuntarlo al tipo mediante el atributo TypeConverterAttribute. De forma predeterminada, esta propiedad de compilación se establece en false cuando se habilita el recorte completo o el AOT nativo.
_MauiBindingInterceptorsSupport Cuando se establece en false, .NET MAUI no intercepta ninguna llamada a los métodos SetBinding y no intentará compilarlas. De manera predeterminada, esta propiedad de compilación está establecida en true.
MauiEnableXamlCBindingWithSourceCompilation Cuando se establece en true, .NET MAUI compilará todos los enlaces, incluidos los donde se usa la Source propiedad . Si habilita esta característica, asegúrese de que todos los enlaces tengan el valor correcto x:DataType para que se compilen o borren el tipo de datos con x:Data={x:Null}} si el enlace no se debe compilar. De forma predeterminada, esta propiedad de compilación se establece en true cuando se habilita el recorte completo o el AOT nativo.
MauiHybridWebViewSupported Cuando se establece en false, el HybridWebView control no estará disponible. De forma predeterminada, esta propiedad de compilación se establece en false cuando se habilita el recorte completo o el AOT nativo.

Estas propiedades de MSBuild también tienen modificadores equivalentes AppContext :

  • La MauiEnableVisualAssemblyScanning propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled.
  • La MauiShellSearchResultsRendererDisplayMemberNameSupported propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported.
  • La MauiQueryPropertyAttributeSupport propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported.
  • La MauiImplicitCastOperatorsUsageViaReflectionSupport propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported.
  • La _MauiBindingInterceptorsSupport propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported.
  • La MauiEnableXamlCBindingWithSourceCompilation propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled.
  • La MauiHybridWebViewSupported propiedad MSBuild tiene un modificador equivalente AppContext denominado Microsoft.Maui.RuntimeFeature.IsHybridWebViewSupported.

La manera más fácil de consumir un modificador de características es colocar la propiedad de MSBuild correspondiente en el archivo de proyecto de la aplicación (*.csproj), lo que hace que el código relacionado se recorte de los ensamblados MAUI de .NET.

Conservar código

Cuando se usa el optimizador, a veces se quita el código al que podría haber llamado dinámicamente, incluso indirectamente. Puede indicar al recortador que conserve los miembros anotando con el DynamicDependency atributo . Este atributo se puede usar para expresar una dependencia en un tipo y subconjunto de miembros, o en miembros específicos.

Importante

Todos los miembros del BCL que no se pueden determinar estáticamente para usar por la aplicación están sujetos a su eliminación.

El atributo DynamicDependency se puede aplicar a constructores, campos y métodos:

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

En este ejemplo, DynamicDependency garantiza que el método Helper se mantenga. Sin el atributo , el recorte se quitaría Helper de MyAssembly o quitaría MyAssembly completamente si no se hace referencia a él en otro lugar.

El atributo especifica los miembros que se mantienen mediante string o mediante el atributo DynamicallyAccessedMembers. El tipo y el ensamblado están implícitos en el contexto del atributo o se especifican explícitamente en el atributo (mediante Type, o mediante objetos string para el tipo y el nombre del ensamblado).

Las cadenas de tipo y miembro usan una variación del formato de cadena de identificador de comentario de documentación de C#, sin el prefijo de miembro. La cadena de miembro no debe incluir el nombre del tipo declarante y puede omitir parámetros para mantener todos los miembros del nombre especificado. En los ejemplos siguientes se muestran usos válidos:

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

Conservar ensamblados

Es posible especificar ensamblados que se deben excluir del proceso de recorte, al tiempo que se permite recortar otros ensamblados. Este enfoque puede ser útil cuando no se puede usar fácilmente el DynamicDependency atributo o no controlar el código que se está recortando.

Cuando recorta todos los ensamblados, puede indicar al optimizador que omita un ensamblado estableciendo un TrimmerRootAssembly elemento de MSBuild en el archivo de proyecto:

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

Nota:

La extensión .dll no es necesaria al establecer la propiedad de MSBuild TrimmerRootAssembly.

Si el optimizador omite un ensamblado, se considera raíz, lo que significa que se conservan todas sus dependencias que se entienden estáticamente. Puedes omitir ensamblados adicionales agregando más propiedades de MSBuild TrimmerRootAssembly a <ItemGroup>.

Conservar ensamblados, tipos y miembros

Puede pasar al recortador un archivo de descripción XML que especifique qué ensamblados, tipos y miembros deben conservarse.

Para excluir un miembro del proceso de recorte al recortar todos los ensamblados, establezca el TrimmerRootDescriptor elemento de MSBuild en el archivo de proyecto en el archivo XML que define los miembros que se van a excluir:

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

A continuación, el archivo XML usa el formato de descriptor de optimizador para definir qué miembros excluir:

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

En este ejemplo, el archivo XML especifica un método al que accede dinámicamente la aplicación, que se excluye del recorte.

Cuando se muestra un ensamblado, un tipo o un miembro en el XML, la acción predeterminada es conservación, lo que significa que, independientemente de si el optimizador cree que se usa o no, se conserva en la salida.

Nota:

Las etiquetas de conservación son ambiguamente inclusivas. Si no proporcionas el siguiente nivel de detalle, incluirá todos los elementos secundarios. Si se muestra un ensamblado sin ningún tipo, se conservarán todos los tipos y miembros del ensamblado.

Marcar un ensamblado como seguro de recorte

Si tiene una biblioteca en el proyecto o es desarrollador de una biblioteca reutilizable y desea que el optimizador trate el ensamblado como recortable, puede marcar el ensamblado como seguro para recortar agregando la IsTrimmable propiedad MSBuild al archivo de proyecto para el ensamblado:

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

Esto marca el ensamblado como "recortable" y habilita las advertencias de recorte para ese proyecto. Ser "recortable" significa que el ensamblado se considera compatible con el recorte y no debe tener advertencias de recorte al compilar el ensamblado. Cuando se usa en una aplicación recortada, el ensamblado tiene sus miembros sin usar recortados en la salida final.

Al usar la implementación de AOT nativa en .NET 9 y versiones posteriores, al establecer la propiedad IsAotCompatible MSBuild en true también se asigna un valor de true a la propiedad IsTrimmable y se habilitan propiedades adicionales de compilación del analizador de AOT. Para obtener más información sobre los analizadores de AOT, consulte analizadores de compatibilidad de AOT. Para obtener más información sobre la implementación nativa AOT para .NET MAUI, consulte implementación nativa AOT.

Al establecer la propiedad de MSBuild IsTrimmable en true en tu archivo del proyecto, se inserta el atributo AssemblyMetadata en el ensamblado:

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

Como alternativa, puedes agregar el atributo AssemblyMetadata al ensamblado sin haber agregado la propiedad de MSBuild IsTrimmable al archivo del proyecto de tu ensamblado.

Nota:

Si la propiedad de MSBuild IsTrimmable está establecida para un ensamblado, esto invalida el atributo AssemblyMetadata("IsTrimmable", "True"). Esto te permite optar por recortar un ensamblado aunque no tenga el atributo o deshabilitar el recorte de un ensamblado que lo tenga.

Supresión de advertencias de análisis de código

Cuando el optimizador está habilitado, quita il que no es accesible estáticamente. Las aplicaciones que usan reflexión u otros patrones que crean dependencias dinámicas pueden romperse como consecuencia de ello. Para advertir sobre estos patrones, al marcar un ensamblado como seguro de recorte, los autores de bibliotecas deben establecer la SuppressTrimAnalysisWarnings propiedad falseMSBuild en :

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

Si no se suprimen las advertencias de análisis de recorte, se incluirán advertencias sobre toda la aplicación, incluido el código propio, el de la biblioteca y el del SDK.

Representación de advertencias detalladas

El análisis de recorte genera como máximo una advertencia para cada ensamblado que procede de un elemento PackageReference, lo que indica que los aspectos internos del ensamblado no son compatibles con el recorte. Como autor de la biblioteca, al marcar un ensamblado como seguro de recorte, debe habilitar advertencias individuales para todos los ensamblados estableciendo la TrimmerSingleWarn propiedad falseMSBuild en :

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

Se muestran todas las advertencias detalladas, en lugar de reducirlas a una única advertencia por ensamblado.

Consulte también