Partilhar via


CA1838: Evite StringBuilder parâmetros para P/Invokes

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 In parâmetro, copie o conteúdo do StringBuilder 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, 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.

Consulte também