Compartilhar via


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

Confira também