修剪 .NET MAUI 應用程式
建置應用程式時,.NET 多平臺應用程式 UI (.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 以取代隱含轉換運算符。
- 使用 LoadFromXaml 擴充方法在運行時間載入 XAML。 這個 XAML 可以藉由批注所有可在運行
DynamicallyAccessedMembers
時間使用 屬性或DynamicDependency
屬性載入的類型,來安全修剪。 不過,這很容易出錯,不建議這麼做。 - 使用 QueryPropertyAttribute接收導覽數據。 相反地,您應該在需要接受查詢參數的類型上實 IQueryAttributable 作 介面。 如需詳細資訊,請參閱 使用單一方法處理導覽數據。
SearchHandler.DisplayMemberName
屬性。 相反地,您應該提供 ItemTemplate 來定義結果的外觀 SearchHandler 。 如需詳細資訊,請參閱 定義搜尋結果項目外觀。
或者,您可以使用功能參數,讓修剪器保留這些功能的程序代碼。 如需詳細資訊,請參閱 修剪功能參數。
如需 .NET 修剪不相容,請參閱 已知修剪不相容。
定義 TypeConverter 以取代隱含轉換運算符
當將不相容類型的值指派給 XAML 中的屬性時,或當啟用完整修剪時,當不同類型的兩個屬性使用數據系結時,不可能依賴隱含轉換運算符。 這是因為如果修剪器未在您的 C# 程式代碼中使用隱含運算符方法,就可以移除這些方法。 如需隱含轉換運算子的詳細資訊,請參閱 使用者定義的明確和隱含轉換運算符。
例如,請考慮下列類型,定義和Size
之間的SizeRequest
隱含轉換運算符:
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# 程式代碼中未使用隱含轉換運算子,和之間的SizeRequest
Size
隱含轉換運算子可能會由修剪器移除。
相反地,您應該為型別定義 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 具有稱為功能參數的修剪器指示詞,可讓您保留非安全修剪功能的程序代碼。 當組建屬性設定為 full
時$(TrimMode)
,也可以使用這些修剪器指示詞,以及 NativeAOT:
MSBuild 屬性 | 描述 |
---|---|
MauiEnableVisualAssemblyScanning |
當設定為 true 時,.NET MAUI 會掃描元件中實作 IVisual 和 [assembly:Visual(...)] 屬性的類型,並將註冊這些類型。 根據預設,此組建屬性會設定為 false 。 |
MauiShellSearchResultsRendererDisplayMemberNameSupported |
當設定為 false 時,將會忽略的值 SearchHandler.DisplayMemberName 。 相反地,您應該提供 ItemTemplate 來定義結果的外觀 SearchHandler 。 根據預設,此組建屬性會設定為 true 。 |
MauiQueryPropertyAttributeSupport |
當設定為 false 時, [QueryProperty(...)] 屬性不會用來在巡覽時設定屬性值。 相反地,您應該實作 IQueryAttributable 介面以接受查詢參數。 根據預設,此組建屬性會設定為 true 。 |
MauiImplicitCastOperatorsUsageViaReflectionSupport |
當設定為 false 時,.NET MAUI 在將值從某個類型轉換成另一個類型時,不會尋找隱含轉換運算符。 這可能會影響具有不同類型之屬性之間的系結,以及設定具有不同型別值的可系結物件屬性值。 相反地,您應該為類型定義 , TypeConverter 並使用 屬性將它附加至類型 TypeConverterAttribute 。 根據預設,此組建屬性會設定為 true 。 |
_MauiBindingInterceptorsSupport |
當設定為 false 時,.NET MAUI 不會攔截對方法的任何呼叫 SetBinding ,也不會嘗試編譯它們。 根據預設,此組建屬性會設定為 true 。 |
MauiEnableXamlCBindingWithSourceCompilation |
當設定為 true 時,.NET MAUI 會編譯所有系結,包括使用 屬性的 Source 系結。 如果啟用這項功能,請確定所有系結都有正確的 x:DataType 編譯,或清除系結不應該編譯時使用 x:Data={x:Null}} 的數據類型。 根據預設,只有在啟用完整修剪或原生 AOT 部署時,才會將此組建屬性設定為 true 。 |
這些 MSBuild 屬性也有對等 AppContext 參數:
MauiEnableVisualAssemblyScanning
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled
對等AppContext參數。MauiShellSearchResultsRendererDisplayMemberNameSupported
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported
對等AppContext參數。MauiQueryPropertyAttributeSupport
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported
對等AppContext參數。MauiImplicitCastOperatorsUsageViaReflectionSupport
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported
對等AppContext參數。_MauiBindingInterceptorsSupport
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported
對等AppContext參數。MauiEnableXamlCBindingWithSourceCompilation
MSBuild 屬性具有名為 的Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled
對等AppContext參數。
取用功能參數最簡單的方式是將對應的 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
會保留。 如果沒有 屬性,如果其他位置未參考,修剪將會 Helper
從 MyAssembly
或移除完全移除 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>
注意
設定 .dll
TrimmerRootAssembly
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>
這會將您的元件標示為「可修剪」,並啟用該專案的修剪警告。 「可修剪」表示您的元件與修剪相容,且建置元件時不應有修剪警告。 在修剪的應用程式中使用時,元件未使用的成員會在最終輸出中移除。
在項目檔中將 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 屬性false
設定TrimmerSingleWarn
為 來啟用所有元件的個別警告:
<PropertyGroup>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
此設定會顯示所有詳細的警告,而不是將它們折疊為每個元件的單一警告。