Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
CA1838: Evite
| Propriedade | valor |
|---|---|
| ID da regra | CA1838 |
| Título | Evite StringBuilder parâmetros para P/Invokes |
| Categoria | Desempenho |
| A correção causa interrupção ou não | Ininterrupto |
| Habilitado por padrão no .NET 10 | Não |
| Línguas aplicáveis | C# e Visual Basic |
Motivo
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
Inparâmetro, copie o conteúdo doStringBuilderpara o buffer nativo. - Se for um
Outparâ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, resolver uma violação envolve adaptar o P/Invoke e os seus chamadores para usar um buffer em lugar 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();
}
Nos casos em que o buffer é pequeno e é aceitável usar o código unsafe, 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é ao tempo de execução, o buffer pode precisar ser criado de forma diferente com base no tamanho para evitar a alocação de grandes buffers com stackalloc.
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.
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 de um arquivo, pasta ou projeto, defina sua gravidade 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.