ข้อจำกัดของการตัดโค้ดและ Reflection
จัดการคำเตือนจากการตัดโค้ด ใช้ [DynamicallyAccessedMembers] และแทนที่ reflection ขณะทำงานด้วยทางเลือกที่สร้างจากซอร์ส
ข้อจำกัดของการตัดโค้ดและ Reflection เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน 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]: แจ้งเตือนผู้เรียกว่าวิธีการหนึ่งไม่ปลอดภัยต่อการตัดโค้ด- เลือกใช้ตัวสร้างซอร์สโค้ดและอินเทอร์เฟซแทนรูปแบบที่พึ่งพารีเฟล็กชันอย่างมาก
เรียนรู้ C# ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 93
- บทเรียน
- 346
คำถามที่พบบ่อย
บทเรียน “ข้อจำกัดของการตัดโค้ดและ Reflection” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ข้อจำกัดของการตัดโค้ดและ Reflection” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ข้อจำกัดของการตัดโค้ดและ Reflection”
จัดการคำเตือนจากการตัดโค้ด ใช้ [DynamicallyAccessedMembers] และแทนที่ reflection ขณะทำงานด้วยทางเลือกที่สร้างจากซอร์ส คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “ข้อจำกัดของการตัดโค้ดและ Reflection” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การคอมไพล์ Native AOT
- ข้อจำกัดของการตัดโค้ดและ Reflection
- ReadyToRun และการคอมไพล์แบบแบ่งระดับ
- การวัดประสิทธิภาพด้วย BenchmarkDotNet