Memperbaiki peringatan pemangkasan

Saat Anda mengaktifkan pemangkasan dalam aplikasi Anda, .NET SDK melakukan analisis statis untuk mendeteksi pola kode yang mungkin tidak kompatibel dengan pemangkasan. Peringatan trim menunjukkan adanya potensi masalah yang dapat menyebabkan perubahan perilaku atau kerusakan sistem setelah pemangkasan.

Aplikasi yang menggunakan pemangkasan tidak boleh menghasilkan peringatan pemangkasan apa pun. Jika ada peringatan pemangkasan, uji aplikasi secara menyeluruh setelah pemangkasan untuk memastikan tidak ada perubahan perilaku.

Artikel ini menyediakan alur kerja praktis untuk mengatasi peringatan pemangkasan. Untuk pemahaman yang lebih mendalam tentang mengapa peringatan ini terjadi dan cara kerja pemangkasan, lihat Memahami analisis pemangkasan.

Memahami kategori peringatan

Peringatan trim terbagi dalam dua kategori utama:

  • Kode tidak kompatibel dengan pemangkasan - Ditandai dengan RequiresUnreferencedCodeAttribute. Kode pada dasarnya tidak dapat dianalisa (misalnya, pemuatan rakitan dinamis atau pola pantulan yang kompleks). Metode ini ditandai sebagai tidak kompatibel, dan penelepon menerima peringatan.

  • Kode dengan persyaratan - Diannotasikan dengan DynamicallyAccessedMembersAttribute. Pantulan digunakan, tetapi jenis diketahui pada waktu kompilasi. Ketika persyaratan terpenuhi, kode menjadi sepenuhnya kompatibel dengan proses pemangkasan.

Alur kerja: Menentukan pendekatan yang tepat

Saat Anda mengalami peringatan pemangkasan, ikuti langkah-langkah berikut secara berurutan:

  1. Hilangkan refleksi - Ini selalu merupakan opsi terbaik jika memungkinkan.
  2. Gunakan DynamicallyAccessedMembers - Jika jenis diketahui, buat kode kompatibel dengan trim.
  3. Gunakan RequiresUnreferencedCode - Jika benar-benar dinamis, dokumentasikan ketidakcocokan.
  4. Tekan peringatan sebagai upaya terakhir - Hanya jika Anda yakin kodenya aman.

Pendekatan 1: Hilangkan refleksi

Solusi terbaik adalah menghindari refleksi sepenuhnya jika memungkinkan. Ini membuat kode Anda lebih cepat dan sepenuhnya kompatibel dengan trim.

Menggunakan generik pada waktu kompilasi

Ganti operasi jenis runtime dengan parameter umum waktu kompilasi:

// ❌ 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...
}

Menggunakan pembuat kode sumber

Modern .NET menyediakan generator sumber untuk skenario refleksi umum:

Untuk informasi selengkapnya, lihat Ketidakcocokan dalam pemangkasan yang diketahui.

Pendekatan 2: Buat kode kompatibel dengan DynamicallyAccessedMembers

Ketika refleksi diperlukan tetapi jenis diketahui pada waktu kompilasi, gunakan DynamicallyAccessedMembersAttribute untuk membuat kode Anda kompatibel dengan pemangkasan.

Langkah demi langkah: Anotasi penggunaan refleksi

Pertimbangkan contoh ini yang menghasilkan peringatan:

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

Langkah 1: Identifikasi operasi refleksi apa yang dilakukan

Kode memanggil GetMethods(), yang memerlukan PublicMethods untuk dipertahankan.

Langkah 2: Anotasi parameter

Tambahkan DynamicallyAccessedMembers untuk memberi tahu pemangkas apa yang diperlukan:

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);
    }
}

Langkah 3: Pastikan penelepon memenuhi persyaratan

Saat memanggil metode ini dengan jenis yang diketahui (typeof), persyaratan secara otomatis terpenuhi:

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

Langkah demi langkah: Menyebarluaskan persyaratan melalui rantai panggilan

Saat tipe mengalir melalui beberapa metode, Anda perlu menyampaikan persyaratan:

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
}

Langkah 1: Mulai dari penggunaan pantulan

Anotasi di mana refleksi benar-benar digunakan:

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

Langkah 2: Menyebarkan rantai panggilan

Bekerja mundur melalui rantai panggilan:

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

Langkah 3: Verifikasi di situs panggilan

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

Untuk detail selengkapnya tentang bagaimana persyaratan mengalir melalui kode, lihat Memahami analisis pemangkasan.

Nilai DynamicallyAccessedMemberTypes umum

Pilih tingkat akses minimum yang diperlukan:

Tipe Anggota Kapan harus menggunakan
PublicConstructors Menggunakan Activator.CreateInstance() atau GetConstructor()
PublicMethods Menggunakan GetMethod() atau GetMethods()
PublicFields Menggunakan GetField() atau GetFields()
PublicProperties Menggunakan GetProperty() atau GetProperties() (serialisasi)
PublicEvents Menggunakan GetEvent() atau GetEvents()

Peringatan

Menggunakan DynamicallyAccessedMemberTypes.All mempertahankan semua anggota pada tipe target dan semua anggota pada tipe berjenjangnya, namun tidak dengan dependensi transitif seperti anggota pada tipe pengembalian properti. Ini secara signifikan meningkatkan ukuran aplikasi. Lebih penting lagi, anggota yang dipertahankan menjadi dapat dijangkau, yang berarti mereka mungkin berisi kode bermasalah mereka sendiri. Misalnya, jika anggota yang dilestarikan memanggil metode yang ditandai dengan RequiresUnreferencedCode, peringatan tersebut tidak dapat diatasi karena anggota disimpan melalui anotasi refleksi bukan melalui panggilan langsung. Gunakan jenis anggota minimum yang diperlukan untuk menghindari masalah berantai ini.

Pendekatan 3: Menandai kode sebagai tidak kompatibel dengan RequiresUnreferencedCode

Ketika kode pada dasarnya tidak dapat diatasi, gunakan RequiresUnreferencedCodeAttribute untuk mendokumen ketidaksesuaian.

Kapan menggunakan RequiresUnreferencedCode

Gunakan atribut ini saat:

  • Jenis dimuat secara dinamis: Menggunakan GetType() dengan string yang ditentukan runtime.
  • Rakitan dimuat saat runtime: Menggunakan LoadFrom(String).
  • Pola refleksi kompleks: Penggunaan pantulan terlalu kompleks untuk dianotasi.
  • Pembuatan kode runtime: Menggunakan System.Reflection.Emit atau dynamic kata kunci.

Langkah demi langkah: Menandai metode yang tidak kompatibel

Langkah 1: Identifikasi kode yang benar-benar tidak kompatibel

Contoh kode yang tidak dapat dibuat kompatibel dengan trim:

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...
}

Langkah 2: Tambahkan atribut 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
}

Langkah 3: Penelepon menerima peringatan

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");
}

Menulis pesan peringatan yang efektif

Pesan RequiresUnreferencedCode yang baik harus:

  • Menyatakan fungsionalitas apa yang tidak kompatibel: Jelaskan dengan spesifik apa yang tidak berfungsi dengan pemangkasan.
  • Sarankan alternatif: Memandu pengembang menuju solusi yang kompatibel dengan trim.
  • Ringkas: Menjaga pesan tetap singkat dan dapat ditindaklanjuti.
// ❌ 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.")]

Untuk panduan yang lebih panjang, tambahkan Url parameter:

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

Menyebarkan RequiresUnreferencedCode

Ketika metode memanggil metode lain yang ditandai dengan RequiresUnreferencedCode, Anda biasanya perlu menyebarluaskan atribut:

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
    }
}

Pola dan solusi umum

Pola: Metode Factory dengan 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);
}

Pola: Sistem plugin memuat rakitan

// 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);
    }
}

Pola: Wadah injeksi dependensi

// 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
    }
}

Pendekatan 4: Menekan peringatan sebagai upaya terakhir

Peringatan

Hanya sembunyikan peringatan trim jika Anda benar-benar yakin kodenya aman. Supresi yang salah dapat menyebabkan kegagalan runtime setelah pemangkasan.

Gunakan UnconditionalSuppressMessageAttribute saat Anda telah memverifikasi bahwa kode tersebut aman terhadap pemangkasan tetapi trim tidak dapat membuktikannya secara statis.

Ketika penindasan sesuai

Sembunyikan peringatan hanya ketika:

  1. Anda telah memastikan semua kode yang diperlukan dipertahankan secara manual (melalui DynamicDependency atau mekanisme lainnya).
  2. Jalur kode tidak pernah dijalankan dalam skenario yang dipangkas.
  3. Anda telah menguji aplikasi yang dioptimalkan secara menyeluruh.

Cara menekan peringatan

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

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

Penting

Jangan gunakan SuppressMessage atau #pragma warning disable untuk trim peringatan. Ini hanya berfungsi untuk pengkompilasi tetapi tidak dipertahankan dalam rakitan yang dikompilasi. Pemangkas beroperasi pada rakitan yang dikompilasi dan tidak akan melihat supresi ini. Selalu gunakan UnconditionalSuppressMessage.

Meminimalkan cakupan penekanan

Terapkan supresi ke cakupan sekecil mungkin. Ekstrak panggilan bermasalah ke dalam fungsi lokal:

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();
    }
}

Pendekatan ini:

  • Menjelaskan panggilan spesifik yang dihilangkan.
  • Mencegah secara tidak sengaja mengabaikan peringatan-peringatan lainnya jika kode berubah.
  • Menjaga pertimbangan dekat dengan panggilan yang ditekan.

Tips pemecahan masalah

Peringatan berlanjut setelah menambahkan DynamicallyAccessedMembers

Pastikan Anda telah membuat anotasi seluruh rantai panggilan dari penggunaan pantulan kembali ke sumber Type:

  1. Temukan di mana pantulan digunakan (seperti GetMethods()).
  2. Anotasi parameter metode tersebut.
  3. Ikuti nilai Type ke belakang melalui semua pemanggilan metode.
  4. Anotasi setiap parameter, bidang, atau parameter jenis generik dalam rantai.

Terlalu banyak peringatan untuk ditangani

  1. Mulailah dengan kode Anda sendiri - perbaiki peringatan dalam kode yang Anda kontrol terlebih dahulu.
  2. Gunakan TrimmerSingleWarn untuk melihat peringatan individual dari paket.
  3. Pertimbangkan apakah pemangkasan sesuai untuk aplikasi Anda.
  4. Periksa Ketidaksesuaian pemangkasan yang diketahui untuk masalah tingkat kerangka kerja.

Tidak yakin DynamicallyAccessedMemberTypes mana yang akan digunakan

LihatLAH API refleksi yang dipanggil:

  • GetMethod() / GetMethods()PublicMethods
  • GetProperty() / GetProperties()PublicProperties
  • GetField() / GetFields()PublicFields
  • GetConstructor() / Activator.CreateInstance() PublicParameterlessConstructor→ atauPublicConstructors
  • GetEvent() / GetEvents()PublicEvents

Gunakan jenis tersempit yang mungkin untuk meminimalkan ukuran aplikasi.

Langkah selanjutnya