Bagikan melalui


CA1838: Hindari parameter StringBuilder untuk P/Invokes

Properti Nilai
ID Aturan CA1838
Judul Hindari StringBuilder parameter untuk P/Invokes
Golongan Performa
Perbaikan bersifat disruptif atau non-disruptif Non-disruptif
Diaktifkan secara default di .NET 8 Tidak

Penyebab

P/Invoke mempunyai parameter StringBuilder.

Deskripsi aturan

Pengaturan StringBuilder selalu membuat sebuah salinan buffer asli, menghasilkan beberapa alokasi untuk satu panggilan P/Invoke. Untuk mengatur StringBuilder sebagai parameter P/Invoke, runtime akan:

  • Mengalokasikan buffer asli.
  • Jika itu adalah parameter In, salin konten dari StringBuilder ke buffer asli.
  • Jika ini adalah parameter Out, salin buffer asli ke dalam larik terkelola yang baru dialokasikan.

Secara default, StringBuilder adalah In dan Out.

Untuk informasi selengkapnya tentang pengaturan string, lihat Pengaturan default untuk string.

Aturan ini dinonaktifkan secara default, karena bisa memerlukan analisis kasus demi kasus apakah pelanggaran menarik serta berpotensi melakukan refaktor non-trivial untuk mengatasi pelanggaran. Pengguna bisa mengaktifkan aturan ini secara eksplisit dengan mengonfigurasi tingkat keparahannya.

Cara memperbaiki pelanggaran

Secara umum, mengatasi pelanggaran melibatkan pengerjaan ulang P/Invoke dan pemanggilnya untuk menggunakan buffer alih-alih StringBuilder. Spesifikasinya akan tergantung pada kasus penggunaan untuk P/Invoke.

Berikut adalah contoh untuk skenario umum penggunaan StringBuilder sebagai sebuah buffer output yang akan diisi oleh fungsi asli:

// Violation
[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo(StringBuilder sb, ref int length);

public void Bar()
{
    int BufferSize = ...
    StringBuilder sb = new StringBuilder(BufferSize);
    int len = sb.Capacity;
    Foo(sb, ref len);
    string result = sb.ToString();
}

Untuk kasus penggunaan di mana buffer kecil dan kode unsafe dapat diterima, stackalloc bisa digunakan untuk mengalokasikan buffer pada tumpukan:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo(char* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        char* buffer = stackalloc char[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
}

Untuk buffer yang lebih besar, array baru bisa dialokasikan sebagai buffer:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = new char[BufferSize];
    int len = buffer.Length;
    Foo(buffer, ref len);
    string result = new string(buffer);
}

Ketika P/Invoke sering dipanggil untuk buffer yang lebih besar, ArrayPool<T> bisa digunakan untuk menghindari alokasi berulang serta tekanan memori yang disertakan dengannya:

[DllImport("MyLibrary", CharSet = CharSet.Unicode)]
private static extern unsafe void Foo([Out] char[] buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    char[] buffer = ArrayPool<char>.Shared.Rent(BufferSize);
    try
    {
        int len = buffer.Length;
        Foo(buffer, ref len);
        string result = new string(buffer);
    }
    finally
    {
        ArrayPool<char>.Shared.Return(buffer);
    }
}

Apabila ukuran buffer tidak diketahui sampai runtime, buffer mungkin perlu dibuat secara berbeda berdasarkan ukuran untuk menghindari alokasi buffer besar dengan stackalloc.

Contoh sebelumnya menggunakan karakter lebar 2-byte (CharSet.Unicode). Jika fungsi asli menggunakan karakter 1-byte (CharSet.Ansi), buffer byte bisa digunakan alih-alih buffer char. Contohnya:

[DllImport("MyLibrary", CharSet = CharSet.Ansi)]
private static extern unsafe void Foo(byte* buffer, ref int length);

public void Bar()
{
    int BufferSize = ...
    unsafe
    {
        byte* buffer = stackalloc byte[BufferSize];
        int len = BufferSize;
        Foo(buffer, ref len);
        string result = Marshal.PtrToStringAnsi((IntPtr)buffer);
    }
}

Jika parameter juga digunakan sebagai input, buffer perlu diisi dengan data string dengan terminator null yang ditambahkan secara eksplisit.

Kapan harus menekan peringatan

Tekan pelanggaran aturan ini jika Anda tidak khawatir tentang dampak performa dari pengaturan StringBuilder.

Menyembunyikan peringatan

Jika Anda hanya ingin menyembunyikan satu pelanggaran, tambahkan arahan praprosedur ke file sumber Anda untuk dinonaktifkan lalu aktifkan kembali aturannya.

#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838

Untuk menonaktifkan aturan untuk file, folder, atau proyek, atur tingkat keparahannya ke none dalam file konfigurasi.

[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none

Untuk informasi selengkapnya, lihat Cara menyembunyikan peringatan analisis kode.

Baca juga