0Pricing
C# Academy · レッスン

トリミングとリフレクションの制限

トリム警告に対処し、[DynamicallyAccessedMembers]を使い、実行時リフレクションをソース生成による代替手段に置き換えます。

「トリミングとリフレクションの制限」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

ILトリミングとは

ILトリミングは、発行時に実行結果から未使用の型、メソッド、アセンブリを削除する最適化です。その結果、実行可能ファイルが小さくなります。Native AOTおよび自己完結型の単一ファイル発行では、自動的に有効になります。

トリミングの有効化

プロジェクトファイルでトリミングを有効にします。トリムモードは、未使用のアセンブリのみをトリミングするpartialから、アセンブリ内の未使用のメンバーもトリミングするfullまで制御できます。

// .csproj
<PropertyGroup>
  <!-- Enable trimming on publish -->
  <PublishTrimmed>true</PublishTrimmed>

  <!-- full: aggressive — trims unused members within assemblies
       partial: conservative — only removes unused assemblies -->
  <TrimMode>full</TrimMode>

  <!-- Treat all trimmer warnings as errors (recommended for CI) -->
  <TrimmerRootAssemblies>MyApp</TrimmerRootAssemblies>
  <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

// Publish:
dotnet publish -c Release -r linux-x64 --self-contained

トリマーの動作

トリマーは、エントリポイント(Main、登録されたサービス、属性)から開始して到達可能性分析を実行します。これらのルートから到達できないコードはすべて削除されます。

// Example: only MyApp.Program.Main is a root
// Code flow:
//   Main → WebApplication.Run → MapGet → MyHandler
//   Everything else is trimmed

// What gets KEPT:
class MyHandler { public string Handle() => "ok"; }

// What gets TRIMMED (if not reached):
class UnusedService { public void DoWork() { } }

// Problem: reflection can access UnusedService at runtime
// But the trimmer can't know that at compile time
// → UnusedService is trimmed → MissingMethodException at runtime

リフレクションとトリミング

リフレクションは、トリミングの互換性問題の主な原因です。トリマーは、Type.GetType()やActivator.CreateInstance()を介してアクセスする型を静的に分析できません。

// PROBLEMATIC: type name comes from config at runtime
var typeName = config["Plugin:Type"]!;
var type = Type.GetType(typeName);      // type may have been trimmed!
var instance = Activator.CreateInstance(type!); // MissingMethodException

// PROBLEMATIC: LINQ expressions with reflection
var query = dbContext.Set<T>()          // T discovered via reflection
    .Where(BuildExpression<T>("Name", "Alice"));

// PROBLEMATIC: attribute scanning
var handlers = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(t => t.HasCustomAttribute<HandlerAttribute>());
// All handler types may be trimmed away

DynamicDependency属性

[DynamicDependency]は、リフレクションを介してアクセスされることがわかっている特定のメンバーを保持するよう、トリマーに指示します。

// Keep all public constructors of MyPlugin:
[DynamicDependency(
    DynamicallyAccessedMemberTypes.PublicConstructors,
    typeof(MyPlugin))]
public static IPlugin CreatePlugin()
    => (IPlugin)Activator.CreateInstance(typeof(MyPlugin))!;

// Keep a specific method by name:
[DynamicDependency("ProcessOrder", typeof(OrderHandler))]
public static void Bootstrap() { }

// Keep everything on a type (use sparingly):
[DynamicDependency(
    DynamicallyAccessedMemberTypes.All,
    typeof(LegacyReflectionHelper))]
public static void EnsurePreserved() { }

DynamicallyAccessedMembersアノテーション

[DynamicallyAccessedMembers]は、パラメーター、フィールド、プロパティに付けて、実行時にリフレクションを介してアクセスされるTypeのメンバーをトリマーに伝えます。

// Annotate a method parameter:
public void Register(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type serviceType)
{
    // trimmer knows to preserve public constructors of whatever Type is passed
    var instance = Activator.CreateInstance(serviceType)!;
}

// Annotate a generic parameter:
public static T Create<
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
    T>() where T : new()
    => new T();

// Annotate a field:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
private Type _modelType = typeof(OrderModel);

RequiresUnreferencedCode

リフレクションに根本的に依存するメソッドには、[RequiresUnreferencedCode]を付けます。これにより呼び出し元に警告が表示され、トリミング時にコードが失敗する可能性があることを知らせます。

// Mark a method that uses reflection internally:
[RequiresUnreferencedCode("Uses Type.GetType() — not safe for trimming")]
public static object LoadPlugin(string typeName)
{
    var type = Type.GetType(typeName)!
        ?? throw new InvalidOperationException($"Type {typeName} not found");
    return Activator.CreateInstance(type)!;
}

// Callers will see a trimmer warning:
// IL2026: Members decorated with [RequiresUnreferencedCode]
// need their callers to be decorated with the same attribute
// or be suppressed with [UnconditionalSuppressMessage]

// The warning reminds callers to use a trim-safe alternative
// or add a DynamicDependency for the types they expect

トリミングに安全な代替手段

現代の.NETには、リフレクションを多用する多くのパターンに対して、トリミングに安全な代替手段があります。トリミングの問題を避けるため、これらを優先してください。

// INSTEAD OF: Type.GetType(name) + Activator.CreateInstance
// USE: registered factories or switch expressions:
public IHandler CreateHandler(string name) => name switch
{
    "order" => new OrderHandler(),
    "email" => new EmailHandler(),
    _       => throw new ArgumentException($"Unknown: {name}")
};

// INSTEAD OF: reflection-based JSON serialization
// USE: source-generated JsonSerializerContext

// INSTEAD OF: attribute scanning via Assembly.GetTypes()
// USE: compile-time source generators

// INSTEAD OF: dynamic proxy libraries (Castle, DispatchProxy)
// USE: source-generated interceptors (.NET 8+)

トリム記述子XML

トリマーが分析できない名前空間全体またはアセンブリ全体を保持するには、XMLルート記述子を使用します。たとえば、リフレクションを多用するサードパーティ製ライブラリが該当します。

<!-- TrimmerRoots.xml -->
<linker>
  <!-- Keep everything in LegacyLib -->
  <assembly fullname="LegacyLib">
    <type fullname="LegacyLib.*" preserve="all" />
  </assembly>

  <!-- Keep a specific type -->
  <assembly fullname="MyApp">
    <type fullname="MyApp.Plugins.PluginLoader" preserve="all" />
  </assembly>
</linker>

// Reference in .csproj:
<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

実践例:トリミングの警告を解決する

実際のコードベースでトリマーの警告を解決するための実践的なワークフローです。

// Step 1: enable analyzer during development
// .csproj:
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

// Step 2: build and read warnings:
// warning IL2026: 'MyMapper.MapDynamic(Type)' uses
// [RequiresUnreferencedCode] via 'Type.GetMethod'

// Step 3: fix the root cause:
// BEFORE:
public void MapDynamic(Type t)
{
    var method = t.GetMethod("MapFrom")!; // IL2026
    method.Invoke(null, null);
}

// AFTER: use an interface instead of reflection
public void Map(IMapper mapper) => mapper.MapFrom();

// Step 4: if fix is not feasible, add annotation and document:
[RequiresUnreferencedCode("Uses reflection: ensure target types are preserved")]
public void MapDynamic(Type t) { /* ... */ }

確認問題

[DynamicallyAccessedMembers]属性は、ILトリマーに何を伝えるのでしょうか。

まとめ:トリミングとリフレクションの制限

主なポイント:

  • トリミングは発行時に到達不能なコードを削除します。リフレクションはこの分析を妨げます
  • <PublishTrimmed>true</PublishTrimmed>で有効にし、最大限の削減効果を得るにはTrimMode=fullを使用します
  • [DynamicDependency]:リフレクションで使用する特定のメンバーを明示的に保持します
  • [DynamicallyAccessedMembers]:Typeパラメーターに付けて、保持すべきメンバーをトリマーに知らせます
  • [RequiresUnreferencedCode]:メソッドがトリミングに安全でないことを呼び出し元に警告します
  • リフレクションを多用するパターンよりも、ソースジェネレーターとインターフェイスを優先してください

よくある質問

「トリミングとリフレクションの制限」レッスンは無料ですか?

はい。「トリミングとリフレクションの制限」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「トリミングとリフレクションの制限」で何を学びますか?

トリム警告に対処し、[DynamicallyAccessedMembers]を使い、実行時リフレクションをソース生成による代替手段に置き換えます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「トリミングとリフレクションの制限」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Native AOTコンパイル
  2. トリミングとリフレクションの制限
  3. ReadyToRunと階層型コンパイル
  4. BenchmarkDotNetによるベンチマーク
← C# Academyに戻る