CA1838: Hindari parameter StringBuilder untuk P/Invokes

Properti Nilai
ID Aturan CA1838
Judul Hindari penggunaan StringBuilder parameter untuk P/Invokes
Kategori Performa
Perbaikan bersifat merusak atau tidak merusak Tidak terputus
Diaktifkan secara default di .NET 10 Tidak
Bahasa yang berlaku C# dan Visual Basic

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 bawaan.
  • Jika itu adalah parameter In, salin konten dari StringBuilder ke buffer asli.
  • Jika ini adalah parameter Out, salin buffer asli ke dalam array terkelola yang baru dialokasikan.

Secara default, StringBuilder adalah In dan Out.

Untuk informasi selengkapnya tentang pemonasian string, lihat Pemonasian default untuk string.

Aturan ini dinonaktifkan secara default, karena bisa memerlukan analisis kasus per kasus untuk menentukan apakah pelanggaran tersebut relevan serta berpotensi memerlukan refaktor yang tidak sepele untuk mengatasinya. 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 dapat dialokasikan sebagai penyangga:

[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

Hapus pelanggaran aturan ini jika Anda tidak khawatir tentang dampak performa dari pemanggilan 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.

Lihat juga