CA1838: Evitare StringBuilder parametri per P/Invoke

Proprietà valore
ID regola CA1838
Titolo Evitare StringBuilder parametri per P/Invoke
Categoria Prestazioni
Correzione che causa un'interruzione o un'interruzione Nessuna interruzione
Abilitato per impostazione predefinita in .NET 8 No

Causa

Un P/Invoke ha un StringBuilder parametro .

Descrizione regola

Il marshalling di StringBuilder crea sempre una copia del buffer nativa, generando più allocazioni per una chiamata P/Invoke. Per effettuare il marshalling di un come StringBuilder parametro P/Invoke, il runtime eseguirà le seguenti attività:

  • Allocare un buffer nativo.
  • Se si tratta di un In parametro, copiare il contenuto di nel StringBuilder buffer nativo.
  • Se si tratta di un Out parametro, copiare il buffer nativo in una matrice gestita appena allocata.

Per impostazione predefinita, StringBuilder è In e Out.

Per altre informazioni sul marshalling delle stringhe, vedere Marshalling predefinito per le stringhe.

Questa regola è disabilitata per impostazione predefinita, perché può richiedere l'analisi caso per caso se la violazione è di interesse e il refactoring potenzialmente non semplice per risolvere la violazione. Gli utenti possono abilitare questa regola in modo esplicito configurandone la gravità.

Come correggere le violazioni

In generale, la risoluzione di una violazione comporta la rielaborazione di P/Invoke e dei relativi chiamanti per l'uso di un buffer anziché di StringBuilder. Le specifiche dipendono dai casi d'uso per P/Invoke.

Di seguito è riportato un esempio per lo scenario comune di utilizzo StringBuilder di come buffer di output da riempire tramite la funzione 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();
}

Per i casi d'uso in cui il buffer è piccolo e unsafe il codice è accettabile, stackalloc può essere usato per allocare il buffer nello stack:

[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);
    }
}

Per i buffer di dimensioni maggiori, è possibile allocare una nuova matrice come 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 il P/Invoke viene spesso chiamato per buffer di dimensioni maggiori, ArrayPool<T> può essere usato per evitare le allocazioni ripetute e la pressione di memoria fornita con essi:

[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 la dimensione del buffer non è nota fino al runtime, potrebbe essere necessario creare il buffer in modo diverso in base alle dimensioni per evitare di allocare buffer di grandi dimensioni con stackalloc.

Negli esempi precedenti vengono usati caratteri wide a 2 byte (CharSet.Unicode). Se la funzione nativa usa caratteri a 1 byte (CharSet.Ansi), è possibile usare un byte buffer anziché un char buffer. Ad esempio:

[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 il parametro viene usato anche come input, i buffer devono essere popolati con i dati stringa con qualsiasi carattere di terminazione Null aggiunto in modo esplicito.

Quando eliminare gli avvisi

Eliminare una violazione di questa regola se non si è interessati all'impatto sulle prestazioni del marshalling di un oggetto StringBuilder.

Eliminare un avviso

Se si vuole eliminare una singola violazione, aggiungere direttive del preprocessore al file di origine per disabilitare e quindi riabilitare la regola.

#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838

Per disabilitare la regola per un file, una cartella o un progetto, impostarne la gravità none su nel file di configurazione.

[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none

Per altre informazioni, vedere Come eliminare gli avvisi di analisi del codice.

Vedi anche