Sdílet prostřednictvím


CA1838: Vyhněte se StringBuilder parametrům volání nespravovaného kódu

Vlastnost Hodnota
ID pravidla CA1838
Název Vyhněte se StringBuilder parametrům pro volání nespravovaného kódu
Kategorie Výkon
Oprava způsobující chybu nebo chybu způsobující chybu Nenarušující
Povoleno ve výchozím nastavení v .NET 8 No

Příčina

Volání nespravovaného kóduStringBuilder parametr.

Popis pravidla

Přiřazování StringBuilder vždy vytvoří nativní kopii vyrovnávací paměti, což vede k několika přidělením pro jedno volání volání volání P/Invoke. Pokud chcete zařadit StringBuilder parametr P/Invoke jako parametr, modul runtime:

  • Přidělení nativní vyrovnávací paměti
  • Pokud se jedná o In parametr, zkopírujte obsah StringBuilder nativní vyrovnávací paměti.
  • Pokud se jedná o Out parametr, zkopírujte nativní vyrovnávací paměť do nově přiděleného spravovaného pole.

Ve výchozím nastavení StringBuilder je In a Out.

Další informace o zařazování řetězců naleznete v tématu Výchozí zařazování řetězců.

Toto pravidlo je ve výchozím nastavení zakázané, protože může vyžadovat případnou analýzu toho, jestli je porušení zásad zájmu, a potenciálně ne triviální refaktoring k vyřešení porušení předpisů. Uživatelé můžou toto pravidlo explicitně povolit konfigurací závažnosti.

Jak opravit porušení

Obecně platí, že řešení porušení zahrnuje přepracování volání nespravovaného kódu a jeho volajících, aby místo toho používali vyrovnávací paměť StringBuilder. Specifika by závisela na případech použití volání nespravovaného kódu.

Tady je příklad pro běžný scénář použití StringBuilder jako výstupní vyrovnávací paměti, kterou má vyplnit nativní funkce:

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

Pro případy použití, kdy je vyrovnávací paměť malá a unsafe kód je přijatelný, lze stackalloc použít k přidělení vyrovnávací paměti v zásobníku:

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

U větších vyrovnávacích pamětí je možné přidělit nové pole jako vyrovnávací paměť:

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

Pokud se volání nespravovaného kódu často volá pro větší vyrovnávací paměti, ArrayPool<T> můžete se vyhnout opakovaným přidělením a zatížení paměti, které s nimi přichází:

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

Pokud velikost vyrovnávací paměti není známa do doby běhu, může být potřeba vytvořit odlišně podle velikosti, aby nedošlo k přidělování velkých vyrovnávacích stackallocpamětí .

V předchozích příkladech se používají 2 bajtové široké znaky (CharSet.Unicode). Pokud nativní funkce používá 1 bajtové znaky (CharSet.Ansi), byte lze místo vyrovnávací paměti použít char vyrovnávací paměť. Příklad:

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

Pokud se parametr používá také jako vstup, musí být vyrovnávací paměti vyplněny řetězcovými daty s libovolným ukončovacím znakem null explicitně přidaným.

Kdy potlačit upozornění

Potlačit porušení tohoto pravidla, pokud vás nezajímá dopad na výkon při zařazování StringBuilder.

Potlačení upozornění

Pokud chcete pouze potlačit jedno porušení, přidejte do zdrojového souboru direktivy preprocesoru, abyste pravidlo zakázali a znovu povolili.

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

Pokud chcete pravidlo pro soubor, složku nebo projekt zakázat, nastavte jeho závažnost v none konfiguračním souboru.

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

Další informace naleznete v tématu Jak potlačit upozornění analýzy kódu.

Viz také