CA1838: Unikaj StringBuilder
parametrów dla parametrów P/Invoke
Właściwości | Wartość |
---|---|
Identyfikator reguły | CA1838 |
Tytuł | Unikaj StringBuilder parametrów dla wywołań P/Invoke |
Kategoria | Wydajność |
Poprawka powodująca niezgodność lub niezgodność | Niezgodność |
Domyślnie włączone na platformie .NET 9 | Nie. |
Przyczyna
P /Invoke ma StringBuilder parametr .
Opis reguły
Marshalling zawsze tworzy natywną kopię buforu StringBuilder
, co powoduje wiele alokacji dla jednego wywołania P/Invoke. Aby przeprowadzić marshaling jako StringBuilder
parametr P/Invoke, środowisko uruchomieniowe będzie:
- Przydziel bufor macierzysty.
- Jeśli jest
In
to parametr, skopiuj zawartośćStringBuilder
obiektu do buforu natywnego. - Jeśli jest
Out
to parametr, skopiuj bufor macierzysty do nowo przydzielonej tablicy zarządzanej.
Domyślnie StringBuilder
wartość to In
i Out
.
Aby uzyskać więcej informacji na temat ciągów marshalingu, zobacz Domyślne marshalling dla ciągów.
Ta reguła jest domyślnie wyłączona, ponieważ może wymagać analizy wielkości liter po przypadku, czy naruszenie jest interesujące i potencjalnie nietrygalne refaktoryzacja w celu rozwiązania naruszenia. Użytkownicy mogą jawnie włączyć tę regułę, konfigurując jej ważność.
Jak naprawić naruszenia
Ogólnie rzecz biorąc, rozwiązanie naruszenia polega na przepracowaniu wywołań P/Invoke i jego obiektach wywołujących, aby użyć buforu zamiast StringBuilder
. Specyfika będzie zależeć od przypadków użycia dla wywołania P/Invoke.
Oto przykład typowego scenariusza użycia StringBuilder
jako buforu wyjściowego, który ma zostać wypełniony przez funkcję natywną:
// 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();
}
W przypadku przypadków użycia, w których bufor jest mały, a unsafe
kod jest akceptowalny, można użyć obiektu stackalloc do przydzielenia buforu na stosie:
[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);
}
}
W przypadku większych można przydzielić nową tablicę jako bufor:
[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);
}
Gdy wywołanie P/Invoke jest często wywoływane dla większych, ArrayPool<T> można użyć go, aby uniknąć powtarzających się alokacji i ciśnienia pamięci, które są z nimi dostarczane:
[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);
}
}
Jeśli rozmiar buforu nie jest znany do czasu wykonania, może być konieczne utworzenie buforu w inny sposób na podstawie rozmiaru, aby uniknąć przydzielania dużych za pomocą stackalloc
polecenia .
W powyższych przykładach są używane znaki o szerokości 2 bajtów (CharSet.Unicode
). Jeśli funkcja natywna używa 1-bajtowych znaków (CharSet.Ansi
), byte
można użyć buforu zamiast buforu char
. Na przykład:
[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);
}
}
Jeśli parametr jest również używany jako dane wejściowe, muszą zostać wypełnione danymi ciągu z jawnie dodanym terminatorem o wartości null.
Kiedy pomijać ostrzeżenia
Pomiń naruszenie tej reguły, jeśli nie martwisz się o wpływ na wydajność marshalingu StringBuilder
.
Pomijanie ostrzeżenia
Jeśli chcesz po prostu pominąć pojedyncze naruszenie, dodaj dyrektywy preprocesora do pliku źródłowego, aby wyłączyć, a następnie ponownie włączyć regułę.
#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838
Aby wyłączyć regułę dla pliku, folderu lub projektu, ustaw jego ważność na none
w pliku konfiguracji.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
Aby uzyskać więcej informacji, zobacz Jak pominąć ostrzeżenia dotyczące analizy kodu.