共用方式為


修剪 .NET MAUI 應用程式

建置應用程式時,.NET 多平臺應用程式 UI (.NET MAUI) 可以使用稱為 ILLink 的連結器,使用稱為修剪的技術來減少應用程式的整體大小。 ILLink 藉由分析編譯程式所產生的中繼程式代碼來減少大小。 它會移除未使用的方法、屬性、欄位、事件、結構及類別,以產生只包含執行應用程式所需的程式代碼和元件相依性的應用程式。

為了防止在修剪應用程式時的行為變更,.NET 會透過修剪警告提供修剪相容性的靜態分析。 修剪器會在找到可能與修剪不相容的程式碼時產生修剪警告。 如果有任何修剪警告,則應該修正,而且應該在修剪之後徹底測試應用程式,以確保沒有任何行為變更。 如需詳細資訊,請參閱 修剪警告簡介。

修剪行為

將組建屬性設定 $(TrimMode)partialfull,即可控制修剪行為:

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

重要

$(TrimMode)建置屬性不應該受到組建組態的條件。 這是因為功能參數會根據建置屬性的值 $(TrimMode) 來啟用或停用,而且所有組建組態中都應該啟用或停用相同的功能,讓您的程式代碼運作方式相同。

full修剪模式會移除應用程式未使用的任何程序代碼。 partial修剪模式會修剪基礎平臺的基類連結庫 (BCL)、基礎平臺的元件(例如 Mono.Android.dllMicrosoft.iOS.dll),以及選擇使用$(TrimmableAsssembly)建置專案修剪的任何其他元件:

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

這相當於在建置組件時,設定 [AssemblyMetadata("IsTrimmable", "True")]

注意

您不需要在應用程式的項目檔中將組建屬性設定 $(PublishTrimmed)true ,因為預設會設定此屬性。

如需更多修剪選項,請參閱 修剪選項

修剪預設值

根據預設,當組建組態設定設定為發行組建時,Android 和 Mac Catalyst 組建會使用部分修剪。 不論組建組態為何,iOS 都會針對任何裝置組建使用部分修剪,而且不會針對模擬器組建使用修剪。

修剪不相容

下列 .NET MAUI 功能與完整修剪不相容,修剪器將會移除:

或者,您可以使用功能參數,讓修剪器保留這些功能的程序代碼。 如需詳細資訊,請參閱 修剪功能參數

如需 .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);
}

啟用完整修剪后,如果在 C# 程式代碼中未使用隱含轉換運算子,和之間的SizeRequestSize隱含轉換運算子可能會由修剪器移除。

相反地,您應該為型別定義 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 屬性 描述
MauiEnableVisualAssemblyScanning 當設定為 true時,.NET MAUI 會掃描元件中實作 IVisual[assembly:Visual(...)] 屬性的類型,並將註冊這些類型。 根據預設,啟用完整修剪時,此組建屬性會設定為 false
MauiShellSearchResultsRendererDisplayMemberNameSupported 當設定為 false時,將會忽略的值 SearchHandler.DisplayMemberName 。 相反地,您應該提供 ItemTemplate 來定義結果的外觀 SearchHandler 。 根據預設,當啟用完整修剪或原生 AOT 時,這個建置屬性會設定為 false
MauiQueryPropertyAttributeSupport 當設定為 false時, [QueryProperty(...)] 屬性不會用來在巡覽時設定屬性值。 相反地,您應該實作 IQueryAttributable 介面以接受查詢參數。 根據預設,當啟用完整修剪或原生 AOT 時,這個建置屬性會設定為 false
MauiImplicitCastOperatorsUsageViaReflectionSupport 當設定為 false時,.NET MAUI 在將值從某個類型轉換成另一個類型時,不會尋找隱含轉換運算符。 這可能會影響具有不同類型之屬性之間的系結,以及設定具有不同型別值的可系結物件屬性值。 相反地,您應該為類型定義 , TypeConverter 並使用 屬性將它附加至類型 TypeConverterAttribute 。 根據預設,當啟用完整修剪或原生 AOT 時,這個建置屬性會設定為 false
_MauiBindingInterceptorsSupport 當設定為 false時,.NET MAUI 不會攔截對方法的任何呼叫 SetBinding ,也不會嘗試編譯它們。 根據預設,此組建屬性會設定為 true
MauiEnableXamlCBindingWithSourceCompilation 當設定為 true時,.NET MAUI 會編譯所有系結,包括使用 屬性的 Source 系結。 如果啟用這項功能,請確定所有系結都有正確的 x:DataType 編譯,或清除系結不應該編譯時使用 x:Data={x:Null}} 的數據類型。 根據預設,當啟用完整修剪或原生 AOT 時,這個建置屬性會設定為 true
MauiHybridWebViewSupported 當設定為 false時, HybridWebView 控件將無法使用。 根據預設,當啟用完整修剪或原生 AOT 時,這個建置屬性會設定為 false

這些 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 會保留。 如果沒有 屬性,如果其他位置未參考,修剪將會 HelperMyAssembly 或移除完全移除 MyAssembly

屬性會指定要透過 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>

注意

設定 .dllTrimmerRootAssembly MSBuild 屬性時,不需要延伸模組。

如果修剪器略過元件,則會將其視為 rooted,這表示會保留它及其所有靜態瞭解的相依性。 您可以將更多 TrimmerRootAssembly MSBuild 屬性新增至 , <ItemGroup>以略過其他元件。

保留元件、類型和成員

您可以傳遞 XML 描述檔來指定需要保留哪些元件、類型和成員的修剪器。

若要在修剪所有元件時從修剪程式排除成員,請將項目檔中的 MSBuild 專案設定 TrimmerRootDescriptor 為定義要排除之成員的 XML 檔案:

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

XML 檔案接著會使用修剪器 描述元格式 來定義要排除的成員:

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

在此範例中,XML 檔案會指定應用程式動態存取的方法,該方法已排除在修剪之外。

當元件、類型或成員列在 XML 中時,預設動作會保留,這表示不論修剪器是否認為它是否使用,都會保留在輸出中。

注意

保留卷標模棱兩可。 如果您未提供下一層的詳細數據,則會包含所有子系。 如果未列出任何型別的元件,則會保留所有元件的類型和成員。

將元件標示為修剪安全

如果您的專案中有連結庫,或您是可重複使用連結庫的開發人員,而且您想要修剪器將元件視為可修剪,您可以將 MSBuild 屬性新增 IsTrimmable 至元件的項目檔,將元件標記為安全修剪:

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

這會將您的元件標示為「可修剪」,並啟用該專案的修剪警告。 「可修剪」表示您的元件與修剪相容,且建置元件時不應有修剪警告。 在修剪的應用程式中使用時,元件未使用的成員會在最終輸出中移除。

在 .NET 9+ 中使用原生 AOT 部署時,將 IsAotCompatible MSBuild 屬性設定為 true 也會將 true 的值指派給 IsTrimmable 屬性,並啟用額外的 AOT 分析器組建屬性。 如需 AOT 分析器的詳細資訊,請參閱 AOT 相容性分析器。 如需 .NET MAUI 原生 AOT 部署的詳細資訊,請參閱 原生 AOT 部署

在項目檔中將 IsTrimmable MSBuild 屬性設定為 true ,會將 AssemblyMetadata 屬性插入元件中:

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

或者,您可以將 屬性新增 AssemblyMetadata 至元件,而不需要將 MSBuild 屬性新增 IsTrimmable 至元件的項目檔。

注意

IsTrimmable如果已為元件設定 MSBuild 屬性,這會覆寫 AssemblyMetadata("IsTrimmable", "True") 屬性。 這可讓您選擇元件進行修剪,即使元件沒有 屬性,或停用具有 屬性的元件修剪。

隱藏分析警告

啟用修剪器時,它會移除無法靜態存取的 IL。 使用反映或其他建立動態相依性模式的應用程式可能會因此中斷。 若要警告這類模式,將元件標示為修剪安全時,連結庫作者應將 SuppressTrimAnalysisWarnings MSBuild 屬性設定為 false

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

不隱藏修剪分析警告將包含整個應用程式的警告,包括您自己的程式代碼、連結庫程式碼和 SDK 程式代碼。

顯示詳細的警告

修剪分析針對來自 PackageReference的每個元件最多會產生一個警告,指出元件的內部元件與修剪不相容。 身為連結庫作者,當您將元件標示為修剪安全時,應該藉由將 MSBuild 屬性TrimmerSingleWarn設定false為 來啟用所有元件的個別警告:

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

此設定會顯示所有詳細的警告,而不是將它們折疊為每個元件的單一警告。

另請參閱