Share via


CA1838: Undvik StringBuilder parametrar för P/Invokes

Property Värde
Regel-ID CA1838
Rubrik Undvik StringBuilder parametrar för P/Invokes
Kategori Prestanda
Korrigeringen är icke-bakåtkompatibel Icke-icke-bryta
Aktiverad som standard i .NET 8 Nej

Orsak

En P/Invoke har en StringBuilder parameter.

Regelbeskrivning

Marshalling av StringBuilder skapar alltid en intern buffertkopia, vilket resulterar i flera allokeringar för ett P/Invoke-anrop. Om du vill konvertera en StringBuilder som en P/Invoke-parameter kommer körningen att:

  • Allokera en intern buffert.
  • Om det är en In parameter kopierar du innehållet i StringBuilder den till den interna bufferten.
  • Om det är en Out parameter kopierar du den interna bufferten till en nyligen allokerad hanterad matris.

Som standard StringBuilder är In och Out.

Mer information om marshallingsträngar finns i Standard marshalling för strängar.

Den här regeln är inaktiverad som standard eftersom den kan kräva analys från fall till fall av huruvida överträdelsen är av intresse och potentiellt icke-trivial refaktorisering för att åtgärda överträdelsen. Användare kan uttryckligen aktivera den här regeln genom att konfigurera dess allvarlighetsgrad.

Så här åtgärdar du överträdelser

I allmänhet handlar det om att omarbeta P/Invoke och dess anropare för att använda en buffert i stället för StringBuilder. Detaljerna beror på användningsfallen för P/Invoke.

Här är ett exempel på det vanliga scenariot med att använda StringBuilder som en utdatabuffert som ska fyllas av den inbyggda funktionen:

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

För användningsfall där bufferten är liten och unsafe koden är acceptabel kan stackalloc användas för att allokera bufferten på stacken:

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

För större buffertar kan en ny matris allokeras som buffert:

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

När P/Invoke ofta anropas för större buffertar ArrayPool<T> kan användas för att undvika de upprepade allokeringar och minnestryck som medföljer dem:

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

Om buffertstorleken inte är känd förrän körningen kan bufferten behöva skapas på ett annat sätt baserat på storleken för att undvika att allokera stora buffertar med stackalloc.

I föregående exempel används 2 byte breda tecken (CharSet.Unicode). Om den inbyggda funktionen använder 1 byte tecken (CharSet.Ansi) kan en byte buffert användas i stället för en char buffert. Till exempel:

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

Om parametern också används som indata måste buffertarna fyllas i med strängdata med valfri null-avslut uttryckligen tillagd.

När du ska ignorera varningar

Undertryck en överträdelse av den här regeln om du inte bryr dig om prestandapåverkan av marshalling av en StringBuilder.

Ignorera en varning

Om du bara vill förhindra en enda överträdelse lägger du till förprocessordirektiv i källfilen för att inaktivera och aktiverar sedan regeln igen.

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

Om du vill inaktivera regeln för en fil, mapp eller ett projekt anger du dess allvarlighetsgrad till none i konfigurationsfilen.

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

Mer information finns i Så här utelämnar du kodanalysvarningar.

Se även