CA1838: StringBuilder
-Parameter for P/Invokes vermeiden
Eigenschaft | Wert |
---|---|
Regel-ID | CA1838 |
Titel | CA1838: StringBuilder -Parameter for P/Invokes vermeiden |
Kategorie | Leistung |
Fix führt oder führt nicht zur Unterbrechung | Nicht unterbrechend |
Standardmäßig in .NET 8 aktiviert | Nein |
Ursache
Ein P/Invoke weist einen StringBuilder-Parameter auf.
Regelbeschreibung
Beim Marshalling von StringBuilder
wird immer eine native Pufferkopie erstellt, sodass mehrere Zuordnungen für einen P/Invoke-Aufruf vorhanden sind. Um ein StringBuilder
als P/Aufruf-Parameter zu marshallen, führt Runtime Folgendes aus:
- Einen nativen Puffer hinzufügen.
- Wenn es sich um einen
In
-Parameter handelt, kopieren Sie den Inhalt desStringBuilder
in den nativen Puffer. - Wenn es sich um einen
Out
/Parameter handelt, kopieren Sie den nativen Puffer in ein neu zugeordnetes verwaltetes Array.
StringBuilder
ist standardmäßig In
und Out
.
Weitere Informationen zum Marshalling von Zeichenfolgen finden Sie unter Standardmäßiges Marshalling für Zeichenfolgen.
Diese Regel ist standardmäßig deaktiviert, da Sie eine fallweise Analyse verlangen kann, ob der Verstoß von Interesse ist, und potenziell eine nicht triviale Neufaktorierung, um den Verstoß zu beheben. Benutzer können diese Regel durch Konfigurieren des Schweregradsexplizit aktivieren.
Behandeln von Verstößen
Im allgemeinen umfasst die Behebung einer Verletzung das erneute Arbeiten von P/Invoke und seinen Aufrufern, um einen Puffer anstelle von StringBuilder
zu verwenden. Die Besonderheiten sind abhängig von den Anwendungsfällen für P/Invoke.
Im folgenden finden Sie ein Beispiel für das gängige Szenario der Verwendung von StringBuilder
als Ausgabepuffer, das von der nativen Funktion aufgefüllt wird:
// 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 Anwendungsfälle, in denen der Puffer klein ist und unsafe
-Code akzeptabel ist, kann stackalloc verwendet werden, um den Puffer im Stapel zuzuordnen:
[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);
}
}
Bei größeren Puffern kann ein neues Array als Puffer zugeordnet werden:
[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);
}
Wenn P/Invoke häufig für größere Puffer aufgerufen wird, kann ArrayPool<T> verwendet werden, um die wiederholten Zuordnungen und die Arbeitsspeicherauslastung zu vermeiden, die in den folgenden Fällen entstehen:
[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);
}
}
Wenn die Puffergröße bis zur Runtime nicht bekannt ist, muss der Puffer möglicherweise basierend auf der Größe unterschiedlich erstellt werden, um die Zuordnung großer Puffer mit stackalloc
zu vermeiden.
In den vorangehenden Beispielen werden 2-Byte breit eZeichen (CharSet.Unicode
) verwendet. Wenn die native Funktion 1-Byte-Zeichen (CharSet.Ansi
) verwendet, kann ein byte
-Puffer anstelle eines char
-Puffers verwendet werden. Beispiel:
[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);
}
}
Wenn der Parameter auch als Eingabe verwendet wird, müssen die Puffer mit den Zeichenfolgen-Daten aufgefüllt werden, wobei ein beliebiger Null-Terminator explizit hinzugefügt wird.
Wann sollten Warnungen unterdrückt werden?
Unterdrücken Sie einen Verstoß gegen diese Regel, wenn Leistungseinbußen durch das Marshalling von StringBuilder
keine Rolle spielen.
Unterdrücken einer Warnung
Um nur eine einzelne Verletzung zu unterdrücken, fügen Sie der Quelldatei Präprozessoranweisungen hinzu, um die Regel zu deaktivieren und dann wieder zu aktivieren.
#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838
Um die Regel für eine Datei, einen Ordner oder ein Projekt zu deaktivieren, legen Sie den Schweregrad in der Konfigurationsdatei auf none
fest.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
Weitere Informationen finden Sie unter Vorgehensweise: Unterdrücken von Codeanalyse-Warnungen.