CA1838: P/Invokes parametrelerinden kaçının StringBuilder

Özellik Değer
Kural Kimliği CA1838
Başlık P/Invoke'ler için StringBuilder parametrelerinden kaçının
Kategori Performans
Düzeltme bozucu ya da bozmayan olabilir Kesintisiz
.NET 10'da varsayılan olarak etkin Hayır
Geçerli diller C# ve Visual Basic

Neden

P /Invoke bir StringBuilder parametresine sahiptir.

Kural açıklaması

öğesinin StringBuilder düzeni her zaman yerel bir arabellek kopyası oluşturur ve bu da bir P/Invoke çağrısı için birden çok ayırmaya neden olur. StringBuilder bir P/Invoke parametresi olarak düzenlemek için, çalışma zamanı şunları yapacaktır:

  • Yerel bir arabellek ayırın.
  • Eğer bu bir In parametreyse, StringBuilder içeriğini yerel arabelleğe kopyalayın.
  • Bu bir Out parametreyse, yerel arabelleği yeni tahsis edilen yönetilen bir diziye kopyalayın.

Varsayılan olarak, StringBuilderIn ve Out şeklindedir.

Dizeleri işlemeye hazırlama hakkında daha fazla bilgi için bkz. Dizeler için varsayılan işlemeye hazırlama.

Bu kural varsayılan olarak devre dışıdır, çünkü ihlalin ilgi çekici olup olmadığının duruma göre analizini ve ihlali ele almak için potansiyel olarak basit olmayan yeniden düzenlemeleri gerektirebilir. Kullanıcılar önem derecesini yapılandırarak bu kuralı açıkça etkinleştirebilir.

İhlalleri düzeltme

Genel olarak, bir ihlalin ele alınması, P/Invoke ve çağıranlarının StringBuilder yerine bir arabellek kullanacak şekilde yeniden düzenlenmesini içerir. Ayrıntılar, P/Invoke için kullanım örneklerine bağlıdır.

Burada, yerel işlev tarafından doldurulacak bir çıkış arabelleği olarak kullanmanın StringBuilder yaygın senaryosuna bir örnek verilmiştir:

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

Arabelleğin küçük ve unsafe kodun kabul edilebilir olduğu kullanım senaryoları için, yığındaki tamponu ayırmak amacıyla stackalloc kullanılabilir.

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

Daha büyük arabellekler için, yeni bir dizi arabellek olarak tahsis edilebilir.

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

P/Invoke daha büyük arabellekler için sık sık çağrıldığında, tekrar eden bellek ayırmalarını ve bu ayırmaların neden olduğu bellek baskısını önlemek için ArrayPool<T> kullanılabilir.

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

Arabellek boyutu çalışma zamanına kadar bilinmiyorsa, büyük arabelleklerin stackalloc ile ayrılmasını önlemek için, arabellek boyutuna göre farklı şekilde oluşturulması gerekebilir.

Yukarıdaki örneklerde 2 bayt genişliğinde karakterler (CharSet.Unicode kullanılır). Yerel işlev 1 bayt karakterler (CharSet.Ansi) kullanıyorsa, bir byte arabelleği, char arabelleği yerine kullanılabilir. Örneğin:

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

Parametre giriş olarak da kullanılıyorsa, arabelleklerin dize verileriyle açıkça eklenen herhangi bir null sonlandırıcıyla doldurulması gerekir.

Uyarıların ne zaman bastırılması gerekiyor?

Bir hazırlama StringBuilderişleminin performans etkisi konusunda endişeniz yoksa, bu kuralın ihlalini bastırın.

Uyarıyı gizleme

Yalnızca tek bir ihlali engellemek istiyorsanız, kuralı devre dışı bırakmak ve sonra yeniden etkinleştirmek için kaynak dosyanıza ön işlemci yönergeleri ekleyin.

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

Bir dosya, klasör veya projenin kuralını devre dışı bırakmak için, yapılandırma dosyasındaki önem derecesini noneolarak ayarlayın.

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

Daha fazla bilgi için bkz . Kod analizi uyarılarını gizleme.

Ayrıca bkz.