CA1838: Vyhněte se parametrům StringBuilder pro P/Invokes (volání nespravovaného kódu)

Vlastnost Hodnota
ID pravidla CA1838
Název Vyhněte se StringBuilder parametrům pro P/Invokes
Kategorie Výkon
Oprava, která může být destruktivní nebo nedestruktivní Nezlomitelný
Povoleno ve výchozím nastavení v .NET 10 Ne
Příslušné jazyky C# a Visual Basic

Příčina

Rozhraní P/InvokeStringBuilder parametr.

Popis pravidla

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

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

Ve výchozím nastavení je StringBuilderIn 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 individuální analýzu případ od případu, zda je porušení relevantní, a potenciálně netriviální refaktoring k vyřešení porušení. 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í P/Invoke a jeho volajících, aby místo StringBuilder používali vyrovnávací paměť. Specifika by závisela na případech použití P/Invoke (platformového volání).

Tady je příklad běžného případu 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 P/Invoke často volá pro větší vyrovnávací paměti, lze ArrayPool<T> použít k tomu, aby se předešlo 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 vyrovnávací paměť vytvořena různě podle velikosti, aby nedošlo k přidělování velkých vyrovnávacích pamětístackalloc.

V předchozích příkladech se používají 2 bajtové široké znaky (CharSet.Unicode). Pokud nativní funkce používá 1bajtové znaky (CharSet.Ansi), lze použít vyrovnávací paměť byte místo vyrovnávací paměti char. 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é