CA1838 : évitez les paramètres StringBuilder pour les P/Invokes

Propriété Value
Identificateur de la règle CA1838
Titre Évitez les paramètres StringBuilder pour les P/Invokes
Catégorie Performances
Le correctif est cassant ou non cassant Sans rupture
Activé par défaut dans .NET 8 Non

Cause

Un P/Invoke a un paramètre StringBuilder.

Description de la règle

Le marshaling de StringBuilder crée toujours une copie de la mémoire tampon native, ce qui entraîne plusieurs allocations pour un seul appel P/Invoke. Pour marshaler un StringBuilder en tant que paramètre P/Invoke, le runtime :

  • alloue une mémoire tampon native.
  • S’il s’agit d’un paramètre In, copiez le contenu de StringBuilder dans la mémoire tampon native.
  • S’il s’agit d’un paramètre Out, copiez la mémoire tampon native dans un tableau managé nouvellement alloué.

Par défaut, StringBuilder est In et Out.

Pour plus d’informations sur le marshaling de chaînes, consultez Marshaling par défaut pour les chaînes.

Cette règle est désactivée par défaut, car elle peut nécessiter une analyse au cas par cas pour déterminer si la violation est intéressante et potentiellement une refactorisation non triviale pour traiter la violation. Les utilisateurs peuvent activer explicitement cette règle en configurant sa gravité.

Comment corriger les violations

En général, l’adressage d’une violation implique de retravailler le P/Invoke et ses appelants pour utiliser une mémoire tampon au lieu de StringBuilder. Les spécificités dépendent des cas d’usage pour le P/Invoke.

Voici un exemple pour le scénario courant d’utilisation de StringBuilder comme mémoire tampon de sortie à remplir par la fonction native :

// 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();
}

Pour les cas d’usage où la mémoire tampon est petite et que le code unsafe est acceptable, stackalloc peut être utilisé pour allouer la mémoire tampon sur la pile :

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

Pour les mémoires tampons plus volumineuses, un nouveau tableau peut être alloué en tant que mémoire tampon :

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

Lorsque le P/Invoke est fréquemment appelé pour des mémoires tampons plus volumineuses, ArrayPool<T> peut être utilisé pour éviter les allocations répétées et la sollicitation de la mémoire qui les accompagne :

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

Si la taille de la mémoire tampon n’est pas connue avant le runtime, la mémoire tampon doit être créée différemment en fonction de la taille pour éviter d’allouer des mémoires tampons volumineuses avec stackalloc.

Les exemples précédents utilisent des caractères de 2 octets (CharSet.Unicode). Si la fonction native utilise des caractères d’un octet (CharSet.Ansi), une mémoire tampon byte peut être utilisée au lieu d’une mémoire tampon char. Par exemple :

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

Si le paramètre est également utilisé comme entrée, les mémoires tampons doivent être renseignées avec les données de chaîne avec n’importe quel terminateur Null explicitement ajouté.

Quand supprimer les avertissements

Supprimez une violation de cette règle si vous ne vous préoccupez pas de l’impact du marshaling d’un StringBuilder.

Supprimer un avertissement

Si vous voulez supprimer une seule violation, ajoutez des directives de préprocesseur à votre fichier source pour désactiver et réactiver la règle.

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

Pour désactiver la règle sur un fichier, un dossier ou un projet, définissez sa gravité sur none dans le fichier de configuration.

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

Pour plus d’informations, consultez Comment supprimer les avertissements de l’analyse de code.

Voir aussi