Dela via


CA1838: Undvik StringBuilder parametrar för P/Invokes

Egenskap Värde
Regel-ID CA1838
Title Undvik StringBuilder parametrar för P/Invokes
Kategori Prestanda
Korrigeringen är antingen invasiv eller icke-invasiv Oumbrytbar
Aktiverad som standard i .NET 10 Nej
Tillämpliga språk C# och Visual Basic

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. För att anropa en StringBuilder som en P/Invoke-parameter kommer körtiden att:

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

Som standardinställning är StringBuilderIn och Out.

Mer information om marshalling av strä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 vid körning kan bufferten behöva skapas på ett annat sätt beroende 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);
    }
}

I de fall där parametern också används som indata måste buffertarna fyllas med strängdata med en nullterminator 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