Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
CA1838: Unikaj
| Właściwości | Wartość |
|---|---|
| Identyfikator reguły | CA1838 |
| Tytuł | Unikaj StringBuilder parametrów dla wywołań P/Invoke |
| Kategoria | Wydajność |
| Poprawka łamiąca lub nienaruszająca | Niezgodność |
| Domyślnie włączone na platformie .NET 10 | Nie. |
| Zastosowane języki | C# i Visual Basic |
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 poddać StringBuilder marshalingowi jako parametrowi P/Invoke, środowisko uruchomieniowe będzie:
- Przydziel bufor macierzysty.
- Jeśli
Injest parametrem, skopiuj zawartośćStringBuilderdo bufora natywnego. - Jeśli jest to parametr
Out, skopiuj bufor macierzysty do nowo przydzielonej tablicy zarządzanej.
Domyślnie StringBuilder to In i Out.
Aby uzyskać więcej informacji na temat marshalingu łańcuchów, zobacz Domyślne marshalling dla łańcuchów.
Ta reguła jest domyślnie wyłączona, ponieważ może wymagać analizy w każdym przypadku z osobna, czy naruszenie jest interesujące i potencjalnie nietrywialnej refaktoryzacji, aby usunąć naruszenie. 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 P/Invoke jest często używane dla większych buforów, ArrayPool<T> można użyć, aby uniknąć powtarzających się alokacji i związanego z nimi obciążenia pamięci:
[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 w czasie wykonywania, może być konieczne utworzenie buforu w inny sposób na podstawie jego rozmiaru, aby uniknąć przydzielania dużych buforów za pomocą stackalloc.
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), można użyć buforu byte 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, bufory muszą zostać wypełnione danymi ciągu z jawnie dodanym znakiem 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.