Partilhar via


CA1838: Evite StringBuilder parâmetros para P/Invokes

Property valor
ID da regra CA1838
Título 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 9 Não

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 StringBuilder 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 stackalloco .

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