CA1838: Evite StringBuilder
parâmetros para P/Invokes
Propriedade | valor |
---|---|
ID da regra | CA1838 |
Cargo | Evite StringBuilder parâmetros para P/Invokes |
Categoria | Desempenho |
A correção está quebrando ou não quebrando | Sem quebra |
Habilitado por padrão no .NET 8 | Não |
Causa
Um P/Invoke tem um StringBuilder parâmetro.
Descrição da regra
O marshalling de StringBuilder
sempre cria uma cópia de buffer nativa, resultando em várias alocações para uma chamada P/Invoke. Para organizar a StringBuilder
como um parâmetro P/Invoke, o tempo de execução irá:
- Aloque um buffer nativo.
- Se for um
In
parâmetro, copie oStringBuilder
conteúdo do para o buffer nativo. - Se for um
Out
parâmetro, copie o buffer nativo para uma matriz gerenciada recém-alocada.
Por padrão, StringBuilder
é In
e Out
.
Para obter mais informações sobre empacotamento de cadeias de caracteres, consulte Empacotamento padrão para cadeias de caracteres.
Essa regra é desabilitada por padrão, porque pode exigir uma análise caso a caso de se a violação é de interesse e uma refatoração potencialmente não trivial para resolver a violação. Os usuários podem habilitar explicitamente essa regra configurando sua gravidade.
Como corrigir violações
Em geral, abordar uma violação envolve retrabalhar o P/Invoke e seus chamadores para usar um buffer em vez de StringBuilder
. As especificidades dependeriam dos casos de uso para o P/Invoke.
Aqui está um exemplo para o cenário comum de usar 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 unsafe
o código é 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 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 o P/Invoke é frequentemente chamado para buffers maiores, ArrayPool<T> pode ser usado para evitar as alocações repetidas e a pressão de memória que vem com eles:
[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 tempo de execução, talvez seja necessário criar o buffer de forma diferente com base no tamanho para evitar a alocação de buffers grandes com stackalloc
o .
Os exemplos anteriores usam caracteres de largura de 2 bytes (CharSet.Unicode
). Se a função nativa usa caracteres de 1 byte (CharSet.Ansi
), um byte
buffer pode ser usado em vez de um char
buffer. 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 da cadeia de caracteres com qualquer terminador nulo explicitamente adicionado.
Quando suprimir avisos
Suprima uma violação desta regra se não estiver preocupado com o impacto no desempenho da organização de um StringBuilder
arquivo .
Suprimir um aviso
Se você quiser apenas suprimir uma única violação, adicione diretivas de pré-processador ao seu arquivo de origem para desativar e, em seguida, reativar 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 para um arquivo, pasta ou projeto, defina sua severidade como none
no arquivo de configuração.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
Para obter mais informações, consulte Como suprimir avisos de análise de código.