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 deStringBuilder
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.