CA1838: Undvik StringBuilder
parametrar för P/Invokes
Property | Värde |
---|---|
Regel-ID | CA1838 |
Title | Undvik StringBuilder parametrar för P/Invokes |
Kategori | Prestanda |
Korrigeringen är icke-bakåtkompatibel | Icke-icke-bryta |
Aktiverad som standard i .NET 9 | 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 iStringBuilder
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.