CA1838: evitar parâmetros StringBuilder
para P/Invokes
Property | Valor |
---|---|
ID da regra | CA1838 |
Título | Evitar parâmetros StringBuilder para P/Invokes |
Categoria | Desempenho |
Correção interruptiva ou sem interrupção | Sem interrupção |
Habilitado por padrão no .NET 8 | Não |
Causa
Um P/Invoke tem um parâmetro StringBuilder.
Descrição da regra
O marshaling de StringBuilder
sempre cria uma cópia de buffer nativa, que resulta em diversas alocações para uma chamada de P/Invoke. Para fazer marshal de um StringBuilder
como um parâmetro P/Invoke, o runtime fará o seguinte:
- Aloque um buffer nativo.
- Se for um parâmetro
In
, copie o conteúdo deStringBuilder
para o buffer nativo. - Se for um parâmetro
Out
, copie o buffer nativo em uma matriz gerenciada recém-alocada.
Por padrão, StringBuilder
é In
e Out
.
Para saber mais sobre cadeias de caracteres de marshalling, confira Marshalling padrão para cadeias de caracteres.
Essa regra é desabilitada por padrão, pois pode exigir análises caso a caso com relação a se a violação é de interesse e, potencialmente, a refatoração não trivial para resolver a violação. Os usuários podem habilitar explicitamente essa regra configurando a gravidade dela.
Como corrigir violações
Em geral, abordar uma violação envolve refazer o P/Invoke e seus chamadores para usar um buffer em vez de StringBuilder
. As especificidades dependeriam dos casos de uso para P/Invoke.
Veja o seguinte exemplo de um cenário de uso comum de StringBuilder
como um buffer de saída a ser preenchido pela função nativa:
// 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();
}
Para casos de uso em que o buffer é pequeno e o código unsafe
é aceitável, stackalloc pode ser usado para alocar o buffer na pilha:
[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);
}
}
Para buffers maiores, uma nova matriz pode ser alocada como o 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);
}
Quando P/Invoke é frequentemente chamado para buffers maiores, ArrayPool<T> pode ser usado para evitar alocações repetidas e a pressão de memória resultante:
[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);
}
}
Se o tamanho do buffer não for conhecido até o runtime, o buffer pode precisar ser criado de maneira diferente com base no tamanho para evitar a alocação de buffers grandes com stackalloc
.
Os exemplos anteriores usam caracteres largos de 2 bytes (CharSet.Unicode
). Se a função nativa usar caracteres de 1 byte (CharSet.Ansi
), um buffer byte
poderá ser usado em vez de um buffer char
. Por exemplo:
[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);
}
}
Se o parâmetro também for usado como entrada, os buffers precisarão ser preenchidos com os dados de cadeia de caracteres com qualquer terminador nulo adicionado explicitamente.
Quando suprimir avisos
É seguro suprimir uma violação dessa regra quando você não está preocupado com o impacto de desempenho relacionado ao uso de marshalling em StringBuilder
.
Suprimir um aviso
Para suprimir apenas uma violação, adicione diretivas de pré-processador ao arquivo de origem a fim de desabilitar e, em seguida, reabilitar a regra.
#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838
Para desabilitar a regra em um arquivo, uma pasta ou um projeto, defina a severidade como none
no arquivo de configuração.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
Para obter mais informações, confira Como suprimir avisos de análise de código.