次の方法で共有


トリミングの警告を修正する

アプリケーションでトリミングを有効にすると、.NET SDK は静的分析を実行して、トリミングと互換性のないコード パターンを検出します。 トリミングの警告は、トリミング後に動作の変更やクラッシュを引き起こす可能性のある潜在的な問題を示します。

トリミングを使用するアプリでは、トリミングの警告は生成されません。 トリミングの警告がある場合は、トリミング後にアプリを十分にテストして、動作の変更がないことを確認します。

この記事では、トリム警告に対処するための実用的なワークフローを提供します。 これらの警告が発生する理由とトリミングのしくみの詳細については、「 トリミング分析について」を参照してください。

警告カテゴリについて

トリミングの警告は、次の 2 つの主要なカテゴリに分類されます。

  • トリミングと互換性のないコード - RequiresUnreferencedCodeAttributeでマークされています。 コードを基本的に分析可能にすることはできません (動的アセンブリの読み込みや複雑なリフレクション パターンなど)。 このメソッドは互換性なしとしてマークされ、呼び出し元は警告を受け取ります。

  • 要件を持つコード - DynamicallyAccessedMembersAttributeで注釈が付けられます。 リフレクションが使用されますが、型はコンパイル時に既知です。 要件が満たされると、コードは完全にトリミング互換になります。

ワークフロー: 適切なアプローチを決定する

トリミング警告が発生した場合は、次の手順に従います。

  1. リフレクションを排除 する - 可能であれば、これは常に最適なオプションです。
  2. DynamicallyAccessedMembers を使用する - 型が既知の場合は、コードのトリミングと互換性を持たせるようにします。
  3. RequiresUnreferencedCode を使用 する - 本当に動的な場合は、非互換性を文書化します。
  4. 警告を最後の手段として抑制 する - コードが安全であると確信している場合にのみ。

アプローチ 1: リフレクションを排除する

最善の解決策は、可能な限りリフレクションを完全に回避することです。 これにより、コードの高速化と完全なトリミング互換性が実現します。

コンパイル時ジェネリックを使用する

ランタイム型の操作をコンパイル時のジェネリック パラメーターに置き換えます。

// ❌ Before: Uses reflection
void CreateAndProcess(Type type)
{
    var instance = Activator.CreateInstance(type);
    // Process instance...
}

// ✅ After: Uses generics
void CreateAndProcess<T>() where T : new()
{
    var instance = new T();
    // Process instance...
}

ソース ジェネレーターを使用する

最新の .NET には、一般的なリフレクション シナリオ用のソース ジェネレーターが用意されています。

詳細については、「既知のトリミングの非互換性」を参照してください。

方法 2: コードをトリミング条件に互換にするための DynamicallyAccessedMembers の使用

リフレクションが必要で、コンパイル時に型がわかっている場合は、 DynamicallyAccessedMembersAttribute を使用してコードのトリミングと互換性を持たせるようにします。

ステップ バイ ステップ: リフレクションの使用法に注釈を付ける

警告を生成する次の例を考えてみましょう。

void PrintMethodNames(Type type)
{
    // ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

手順 1: 実行されるリフレクション操作を特定する

このコードは GetMethods()を呼び出します。この場合、 PublicMethods を保持する必要があります。

手順 2: パラメーターに注釈を付ける

DynamicallyAccessedMembersを追加して、トリマーに必要な情報を伝えます。

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    // ✅ No warning - trimmer knows to preserve public methods
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

手順 3: 呼び出し元が要件を満たしていることを確認する

既知の型 (typeof) でこのメソッドを呼び出すと、要件は自動的に満たされます。

// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));

ステップ バイ ステップ: 呼び出しチェーンを介して要件を伝達する

型が複数のメソッドを通過する場合は、要件を伝達する必要があります。

void Method1()
{
    Method2<DateTime>();  // ⚠️ Warning: Generic parameter needs annotation
}

void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);  // ⚠️ Warning: Argument doesn't satisfy requirements
}

void Method3(Type type)
{
    var methods = type.GetMethods();  // ⚠️ Warning: Reflection usage
}

手順 1: リフレクションの使用から開始する

リフレクションが実際に使用される場所に注釈を付けます。

void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();  // ✅ Fixed
}

手順 2: 呼び出しチェーンを伝播させる

呼び出しチェーンを逆に辿ります。

void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    Type t = typeof(T);
    Method3(t);  // ✅ Fixed - T is annotated
}

手順 3: 通話サイトで確認する

void Method1()
{
    Method2<DateTime>();  // ✅ Fixed - DateTime's public methods preserved
}

要件がコードを通過する方法の詳細については、「 トリミング分析について」を参照してください。

よく使われる DynamicallyAccessedMemberTypes 値

必要な最小アクセス レベルを選択します。

メンバーの種類 いつ使用するか
PublicConstructors Activator.CreateInstance()またはGetConstructor()
PublicMethods GetMethod()またはGetMethods()
PublicFields GetField()またはGetFields()を使用して
PublicProperties GetProperty()またはGetProperties()の使用 (シリアル化)
PublicEvents GetEvent()またはGetEvents()

警告

DynamicallyAccessedMemberTypes.Allを使用すると、ターゲット型のすべてのメンバーとその入れ子になった型のすべてのメンバーが保持されます (ただし、プロパティの戻り値の型のメンバーのような推移的な依存関係は保持されません)。 これにより、アプリのサイズが大幅に増加します。 さらに重要なのは、保持されるメンバーがアクセス可能になるということです。これにより、それらのメンバーに独自の問題のあるコードが含まれている可能性があります。 たとえば、保持されているメンバーが RequiresUnreferencedCode でマークされたメソッドを呼び出す場合、メンバーは明示的な呼び出しではなくリフレクション注釈を使用して保持されているため、その警告を解決できません。 これらの連鎖的な問題を回避するには、最低限必要なメンバー型を使用します。

方法 3: コードを RequiresUnreferencedCode と互換性のないものとしてマークする

コードを基本的に分析できない場合は、 RequiresUnreferencedCodeAttribute を使用して非互換性を文書化します。

RequiresUnreferencedCode を使用する場合

この属性は、次の場合に使用します。

  • 型は動的に読み込まれます。ランタイムによって決定された文字列で GetType() を使用します。
  • アセンブリは実行時に読み込まれます: LoadFrom(String)を使用します。
  • 複雑なリフレクション パターン: リフレクションの使用が複雑すぎて注釈を付けることができません。
  • ランタイム コード生成: System.Reflection.Emit または dynamic キーワードの使用。

ステップ バイ ステップ: 互換性のないメソッドをマークする

手順 1: 真に互換性のないコードを特定する

トリム互換にできないコードの例:

void LoadPluginByName(string pluginName)
{
    // Type name comes from runtime input - trimmer cannot know what types are needed
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // Use plugin...
}

手順 2: RequiresUnreferencedCode 属性を追加する

[RequiresUnreferencedCode("Plugin loading by name is not compatible with trimming. Consider using compile-time plugin registration instead.")]
void LoadPluginByName(string pluginName)
{
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // ✅ No warnings inside this method - it's marked as incompatible
}

手順 3: 呼び出し元が警告を受け取る

void InitializePlugins()
{
    // ⚠️ IL2026: Using member 'LoadPluginByName' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. Plugin loading by name is not
    // compatible with trimming. Consider using compile-time plugin registration instead.
    LoadPluginByName("MyPlugin");
}

有効な警告メッセージの書き込み

適切な RequiresUnreferencedCode メッセージは次のとおりです。

  • 互換性のない機能を指定する: トリミングで動作しない機能について具体的に説明します。
  • 代替案を提案する: 開発者をトリム互換ソリューションに導く。
  • 簡潔にする: メッセージを短くして実用的な状態に保ちます。
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]

// ✅ Helpful - explains problem and suggests alternative
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming. Use generic type parameters or source generators instead.")]

より長いガイダンスについては、 Url パラメーターを追加します。

[RequiresUnreferencedCode(
    "Plugin system is not compatible with trimming. See documentation for alternatives.",
    Url = "https://docs.example.com/plugin-trimming")]

非参照コード要件の伝播

メソッドが RequiresUnreferencedCode でマークされた別のメソッドを呼び出す場合は、通常、属性を伝達する必要があります。

class PluginSystem
{
    // Use a constant for consistent messaging
    const string PluginMessage = "Plugin system is not compatible with trimming. Use compile-time registration instead.";

    [RequiresUnreferencedCode(PluginMessage)]
    private void LoadPluginImplementation(string name)
    {
        // Low-level plugin loading
    }

    [RequiresUnreferencedCode(PluginMessage)]
    public void LoadPlugin(string name)
    {
        LoadPluginImplementation(name);  // ✅ No warning - method is also marked
    }
}

一般的なパターンとソリューション

パターン: Activator.CreateInstance を使用するファクトリ メソッド

// ❌ Before: Produces warning
object CreateInstance(Type type)
{
    return Activator.CreateInstance(type);
}

// ✅ After: Trim-compatible
object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
    return Activator.CreateInstance(type);
}

パターン: アセンブリを読み込むプラグイン システム

// This pattern is fundamentally incompatible with trimming
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider compile-time plugin registration using source generators.")]
void LoadPluginsFromDirectory(string directory)
{
    foreach (var dll in Directory.GetFiles(directory, "*.dll"))
    {
        Assembly.LoadFrom(dll);
    }
}

パターン: 依存性注入コンテナー

// Complex DI containers are often incompatible
class Container
{
    [RequiresUnreferencedCode("Service resolution uses complex reflection. Consider using source-generated DI or registering services explicitly.")]
    public object Resolve(Type serviceType)
    {
        // Complex reflection to resolve dependencies
    }
}

方法 4: 最後の手段として警告を抑制する

警告

コードが安全であると確信している場合にのみ、トリミング警告を抑制します。 抑制が正しくないと、トリミング後に実行時エラーが発生する可能性があります。

コードがトリム セーフであることを確認したが、トリマーで静的に証明できない場合は、 UnconditionalSuppressMessageAttribute を使用します。

抑制が適切な場合

次の場合にのみ警告を抑制します。

  1. 必要なすべてのコードが ( DynamicDependency またはその他のメカニズムを使用して) 保持されるように手動で確認しました。
  2. トリミングされたシナリオでは、コード パスは実行されません。
  3. トリミングされたアプリケーションを十分にテストしました。

警告が表示されないようにする方法

[RequiresUnreferencedCode("Uses reflection")]
void MethodWithReflection() { /* ... */ }

[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
    Justification = "All referenced types are manually preserved via DynamicDependency attributes")]
void CallerMethod()
{
    MethodWithReflection();  // Warning suppressed
}

重要

トリミングの警告には SuppressMessage または #pragma warning disable を使用しないでください。 これらはコンパイラでのみ機能しますが、コンパイルされたアセンブリには保持されません。 トリマーはコンパイル済みアセンブリで動作し、これらの抑制は表示されません。 常に UnconditionalSuppressMessage を使用します。

抑制スコープを最小限に抑える

可能な限り最小のスコープに抑制を適用します。 問題のある呼び出しをローカル関数に抽出します。

void ProcessData()
{
    InitializeData();

    CallReflectionMethod();  // Only this call is suppressed

    ProcessResults();

    [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
        Justification = "Types are preserved via DynamicDependency on ProcessData method")]
    void CallReflectionMethod()
    {
        MethodWithReflection();
    }
}

この方法の特徴は次のとおりです。

  • 抑制される特定の呼び出しを明確にします。
  • コードが変更された場合に他の警告を誤って抑制するのを防ぎます。
  • 理由を抑制された呼び出しの近くに保持します。

トラブルシューティングのヒント

DynamicallyAccessedMembers を追加した後も警告が保持される

リフレクションの使用からTypeのソースまで、呼び出しチェーン全体に注釈を付けたことを確認してください。

  1. リフレクションが使用されている場所 ( GetMethods()など) を検索します。
  2. そのメソッドのパラメーターに注釈を付けます。
  3. Typeの値をたどって、すべてのメソッド呼び出しを逆に追跡します。
  4. チェーン内の各パラメーター、フィールド、またはジェネリック型パラメーターに注釈を付けます。

対応すべき警告が多すぎます

  1. 独自のコードから始める - 最初に制御するコードの警告を修正します。
  2. パッケージからの個々の警告を表示するには、 TrimmerSingleWarn を使用します。
  3. トリミングがアプリケーションに適しているかどうかを検討してください。
  4. フレームワークレベルの既知のトリミング非互換性を確認します。

使用するべき DynamicallyAccessedMemberTypes がわからない

呼び出されているリフレクション API を確認します。

  • GetMethod() / GetMethods()PublicMethods
  • GetProperty() / GetProperties()PublicProperties
  • GetField() / GetFields()PublicFields
  • GetConstructor() / Activator.CreateInstance()PublicParameterlessConstructor または PublicConstructors
  • GetEvent() / GetEvents()PublicEvents

可能な限り狭い種類を使用して、アプリのサイズを最小限に抑えます。

次のステップ