.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")]
を設定することと同じです。
Note
既定で設定されているため、 $(PublishTrimmed)
ビルド プロパティをアプリのプロジェクト ファイルに true
するように設定する必要はありません。
トリミング オプションの詳細については、「 トリミング オプションを参照してください。
トリミングの既定値
既定では、ビルド構成がリリース ビルドに設定されている場合、Android および Mac Catalyst ビルドでは部分トリミングが使用されます。 iOS では、ビルド構成に関係なく、デバイス ビルドの部分トリミングが使用され、シミュレーター ビルドではトリミングは使用されません。
互換性のないトリミング
次の .NET MAUI 機能は、フル トリミングと互換性がありません。トリマによって削除されます。
- バインド パスが文字列に設定されているバインド式。 代わりに、コンパイル済みバインディングを使用します。 詳しくは、「コンパイル済みのバインド」を参照してください。
- 暗黙的な変換演算子。XAML のプロパティに互換性のない型の値を割り当てる場合、または異なる型の 2 つのプロパティがデータ バインディングを使用する場合。 代わりに、型の TypeConverter を定義し、 TypeConverterAttributeを使用して型にアタッチする必要があります。 詳細については、「 暗黙的な変換演算子を置き換える TypeConverter を定義するを参照してください。
- LoadFromXaml拡張メソッドを使用して実行時に XAML を読み込む。 この XAML は、実行時に読み込むことができるすべての型に
DynamicallyAccessedMembers
属性またはDynamicDependency
属性を使用して注釈を付けることで、トリミングを安全にすることができます。 ただし、これは非常にエラーが発生しやすく、推奨されません。 - QueryPropertyAttributeを使用してナビゲーション データを受信する。 代わりに、クエリ パラメーターを受け入れる必要がある型に IQueryAttributable インターフェイスを実装する必要があります。 詳細については、「単一のメソッドを使用してナビゲーションデータを処理する」を参照してください。
SearchHandler.DisplayMemberName
プロパティ。 代わりに、SearchHandler の結果の外観を定義する ItemTemplate を指定する必要があります。 詳細については、「 検索結果アイテムの外観を定義する」を参照してください。- 動的
System.Text.Json
シリアル化機能を使用するため、HybridWebView コントロール。
または、機能スイッチを使用して、トリマがこれらの機能のコードを保持するようにすることもできます。 詳細については、「 機能スイッチを参照してください。
.NET トリミングの非互換性については、 非互換性のトリミングに関するを参照してください。
暗黙的な変換演算子を置き換える TypeConverter を定義する
互換性のない型の値を XAML のプロパティに割り当てる場合、または異なる型の 2 つのプロパティでデータ バインディングを使用する場合は、完全トリミングが有効になっている場合、暗黙的な変換演算子に依存することはできません。 これは、暗黙的な演算子メソッドが 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);
}
完全トリミングを有効にすると、 SizeRequest
と Size
の間の暗黙的な変換演算子が、C# コードで使用されていない場合はトリマーによって削除される可能性があります。
代わりに、型の 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 の値は無視されます。 代わりに、SearchHandler の結果の外観を定義する ItemTemplate を指定する必要があります。 既定では、このビルド プロパティは、フル トリミングまたはネイティブ 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 プロパティには、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スイッチがあります。MauiHybridWebViewSupported
MSBuild プロパティには、Microsoft.Maui.RuntimeFeature.IsHybridWebViewSupported
という名前の同等の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
メソッドが保持されていることを確認します。 属性がないと、トリミングによってMyAssembly
からHelper
が削除されるか、他の場所で参照されていない場合はMyAssembly
が完全に削除されます。
この属性では、string
または DynamicallyAccessedMembers
属性を介して保持するメンバーを指定します。 型とアセンブリは、属性コンテキスト内で暗黙的に指定されるか、または属性内で明示的に指定されます (Type
によって、または型とアセンブリ名の場合は string
によって)。
型とメンバーの文字列では、C# ドキュメントのコメント ID 文字列の形式 のバリエーションを、メンバー プレフィックスなしで使用します。 メンバー文字列には、宣言する型の名前を含めることはできません。指定した名前のすべてのメンバーを保持するにはパラメーターを省略してかまいません。 次の例は、有効な用途を示しています。
[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
属性を簡単に使用できない場合や、トリミングされるコードを制御できない場合に便利です。
すべてのアセンブリをトリミングするときに、プロジェクト ファイルで msBuild 項目 TrimmerRootAssembly
設定することで、アセンブリをスキップするようにトリマーに指示できます。
<ItemGroup>
<TrimmerRootAssembly Include="MyAssembly" />
</ItemGroup>
Note
TrimmerRootAssembly
MSBuild プロパティを設定するときに、.dll
拡張機能は必要ありません。
トリマーがアセンブリをスキップすると、 rootと見なされます。つまり、そのアセンブリとその静的に認識されたすべての依存関係が保持されます。 <ItemGroup>
に TrimmerRootAssembly
MSBuild プロパティを追加することで、追加のアセンブリをスキップできます。
アセンブリ、型、メンバーを保持する
保持する必要があるアセンブリ、型、およびメンバーを指定する XML 記述ファイルをトリマーに渡すことができます。
すべてのアセンブリをトリミングするときにメンバーをトリミング プロセスから除外するには、プロジェクト ファイル内の TrimmerRootDescriptor
MSBuild 項目を、除外するメンバーを定義する XML ファイルに設定します。
<ItemGroup>
<TrimmerRootDescriptor Include="MyRoots.xml" />
</ItemGroup>
次に、XML ファイルはトリマー descriptor 形式を使用して 除外するメンバーを定義します。
<linker>
<assembly fullname="MyAssembly">
<type fullname="MyAssembly.MyClass">
<method name="DynamicallyAccessedMethod" />
</type>
</assembly>
</linker>
この例では、XML ファイルは、トリミングから除外される、アプリによって動的にアクセスされるメソッドを指定します。
アセンブリ、型、またはメンバーが XML にリストされている場合、既定のアクションは保持です。つまり、トリマーが使用されていると見なすかどうかにかかわらず、出力に保持されます。
Note
保持タグはあいまいに包括的です。 次のレベルの詳細を指定しない場合は、すべての子が含まれます。 アセンブリが型なしで一覧表示されている場合は、アセンブリのすべての型とメンバーが保持されます。
アセンブリをトリム セーフとしてマークする
プロジェクトにライブラリがある場合、または再利用可能なライブラリの開発者で、トリマでアセンブリをトリミング可能として扱いたい場合は、アセンブリのプロジェクト ファイルに IsTrimmable
MSBuild プロパティを追加することで、アセンブリをトリム セーフとしてマークできます。
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
これにより、アセンブリが「トリミング可能」としてマークされ、そのプロジェクトに対するトリミングの警告が有効になります。 「トリミング可能」とは、ライブラリがトリミングと互換性があると見なされ、ライブラリのビルド時にトリミングの警告が発生しないはずであることを意味します。 トリミングされたアプリで使用されたとき、最終的な出力において、アセンブリではその未使用のメンバーが削除されます。
プロジェクト ファイルで IsTrimmable
MSBuild プロパティを true
に設定すると、アセンブリに AssemblyMetadata
属性が挿入されます。
[assembly: AssemblyMetadata("IsTrimmable", "True")]
または、アセンブリのプロジェクト ファイルに IsTrimmable
MSBuild プロパティを追加せずに、アセンブリに AssemblyMetadata
属性を追加することもできます。
Note
アセンブリに対して IsTrimmable
MSBuild プロパティが設定されている場合は、AssemblyMetadata("IsTrimmable", "True")
属性がオーバーライドされます。 これにより、属性がない場合でもアセンブリのトリミングを選択したり、属性を持つアセンブリのトリミングを無効にしたりすることができます。
分析の警告を抑制する
トリマーが有効になっていると、静的に到達できない IL が削除されます。 リフレクションまたは動的な依存関係を作成するその他のパターンを使用するアプリは、結果として壊れることがあります。 このようなパターンに関する警告を表示するには、アセンブリをトリム セーフとしてマークする場合、ライブラリ作成者は、 SuppressTrimAnalysisWarnings
MSBuild プロパティを false
に設定する必要があります。
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
トリミング分析の警告を抑制しない場合、独自のコード、ライブラリ コード、SDK コードを含む、アプリ全体に関する警告が含まれます。
詳細な警告の表示
トリミング分析では、アセンブリの内部構造とトリミングに互換性がないことを示す PackageReference
からの警告が、アセンブリごとに最大で 1 つ生成されます。 ライブラリの作成者は、アセンブリをトリム セーフとしてマークする場合は、 TrimmerSingleWarn
MSBuild プロパティを false
に設定して、すべてのアセンブリに対して個別の警告を有効にする必要があります。
<PropertyGroup>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
この設定により、アセンブリごとに 1 つの警告に折りたたむのではなく、すべての詳細な警告を表示します。
関連項目
.NET MAUI