Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
CA1838: не используйте параметры
| Свойство | Значение |
|---|---|
| Идентификатор правила | CA1838 |
| Заголовок | Избегайте StringBuilder параметров при использовании P/Invokes |
| Категория | Производительность |
| Исправление является критическим или не критическим | неразрывный |
| Включен по умолчанию в .NET 10 | Нет |
| Применимые языки | C# и Visual Basic |
Причина
P/Invoke имеет параметр StringBuilder.
Описание правила
Маршаллирование StringBuilder всегда создает собственную копию буфера, что приводит к нескольким выделениям для одного вызова P/Invoke. Чтобы маршалировать StringBuilder как параметр P/Invoke, среда выполнения выполняет следующие действия.
- Выделите нативный буфер.
- Если это параметр
In, копирует содержимоеStringBuilderв собственный буфер. - Если параметр
Out, скопируйте собственный буфер в недавно выделенный управляемый массив.
По умолчанию параметр StringBuilder является In и Out.
Дополнительные сведения о маршаллинге строк см. в разделе Маршалинг строк по умолчанию.
Это правило отключено по умолчанию, так как оно может потребовать выполнения индивидуального анализа того, представляет ли нарушение интерес, и потенциально сложного рефакторинга для устранения нарушения. Пользователи могут явно включить это правило, настроив его уровень серьезности.
Устранение нарушений
В целом, чтобы устранить нарушение, необходимо переработать P/Invoke и его вызывающие объекты, чтобы вместо StringBuilder использовался буфер. Конкретные детали зависят от вариантов использования P/Invoke.
Ниже приведен пример типичного сценария использования StringBuilder в качестве выходного буфера, заполняемого собственной функцией.
// 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();
}
В тех вариантах использования, когда буфер невелик и код unsafe приемлем, можно использовать stackalloc для выделения буфера в стеке:
[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);
}
}
Для буферов большего размера можно выделить в качестве буфера новый массив:
[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);
}
Если метод P/Invoke часто вызывается для буферов большего размера, можно использовать ArrayPool<T>, чтобы избежать повторного выделения памяти и связанной с ним чрезмерной нагрузки на память.
[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);
}
}
Если размер буфера неизвестен до времени выполнения, то, возможно, буфер придется создавать по-разному в зависимости от размера, чтобы избежать выделения больших буферов с помощью stackalloc.
В приведенных выше примерах используются двухбайтовые символы (CharSet.Unicode). Если собственная функция использует однобайтовые символы (CharSet.Ansi), то вместо буфера byte можно использовать буфер char. Например:
[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);
}
}
Если параметр также используется в качестве входных данных, буферы должны быть заполнены строковыми данными с любым нуль-символом конца, добавленным явным образом.
Когда лучше отключить предупреждения
Подавить нарушение этого правила, если вы не обеспокоены воздействием на производительность управления StringBuilder.
Отключение предупреждений
Если вы просто хотите отключить одно нарушение, добавьте директивы препроцессора в исходный файл, чтобы отключить и повторно включить правило.
#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838
Чтобы отключить правило для файла, папки или проекта, задайте его серьезность none в файле конфигурации.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
Дополнительные сведения см. в разделе Практическое руководство. Скрытие предупреждений анализа кода.