트리밍과 리플렉션의 제한 사항
트리밍 경고를 처리하고 [DynamicallyAccessedMembers]를 사용하며 런타임 리플렉션을 소스 생성 대안으로 바꿉니다.
트리밍과 리플렉션의 제한 사항은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
IL 트리밍이란 무엇입니까
IL 트리밍은 게시 시점에 수행되는 최적화로, 사용하지 않는 형식, 메서드 및 어셈블리를 출력에서 제거합니다. 그 결과 실행 파일이 더 작아집니다. 네이티브 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 awayDynamicDependency 특성
[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/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“트리밍과 리플렉션의 제한 사항”에서 뭘 배우나요?
트리밍 경고를 처리하고 [DynamicallyAccessedMembers]를 사용하며 런타임 리플렉션을 소스 생성 대안으로 바꿉니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“트리밍과 리플렉션의 제한 사항” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 네이티브 AOT 컴파일
- 트리밍과 리플렉션의 제한 사항
- ReadyToRun과 계층형 컴파일
- BenchmarkDotNet을 사용한 벤치마킹