CA1838: P/Invokes に StringBuilder パラメーターを使用しません

プロパティ
ルール ID CA1838
Title P/Invokes に StringBuilder パラメーターを使用しません
[カテゴリ] パフォーマンス
修正が中断ありか中断なしか なし
.NET 8 では既定で有効 いいえ

原因

P/Invoke には StringBuilder パラメーターがあります。

規則の説明

StringBuilder をマーシャリングすると、ネイティブ バッファーのコピーが常に作成され、1 回の P/Invoke 呼び出しに対して複数の割り当てが発生します。 StringBuilder を P/Invoke パラメーターとしてマーシャリングするために、ランタイムは次を実行します。

  • ネイティブ バッファーを割り当てます。
  • それが In パラメーターの場合、StringBuilder の内容を ネイティブ バッファーにコピーします。
  • Out パラメーターの場合は、新しく割り当てられたマネージ配列にネイティブ バッファーをコピーします。

既定では、StringBuilderInOut です。

文字列のマーシャリングの詳細については、「文字列に対する既定のマーシャリング」参照してください。

この規則は、その違反が対象のものであるかどうか、また違反に対処するためのリファクタリングが大掛かりなものになる可能性があるかどうかをケースごとに分析する必要があるため、既定で無効になっています。 ユーザーは、その重要度を構成することによって、この規則を明示的に有効にできます。

違反の修正方法

通常、違反に対処するには、StringBuilder ではなくバッファーを使用するように、P/Invoke とその呼び出し元について再作業する必要があります。 詳細は、P/Invoke のユース ケースによって異なります。

ネイティブ関数で埋める出力バッファーとして StringBuilder を使用する一般的なシナリオの例を次に示します。

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

バッファーが小さく、unsafe コードが許容されるユース ケースでは、stackalloc を使用してスタックにバッファーを割り当てることができます。

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

より大きなバッファーの場合、新しい配列をバッファーとして割り当てることができます。

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

大きなバッファーに対して P/Invoke が頻繁に呼び出される場合、ArrayPool<T> を使用して、割り当てが繰り返されないようにし、それに伴うメモリ不足が発生しないようにすることができます。

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

バッファー サイズが実行時までわからない場合は、サイズに基づいてバッファーを作成し、大きなバッファーが stackalloc で割り当てられないようにする必要があります。

前の例では、2 バイトのワイド文字 (CharSet.Unicode) を使用しています。 ネイティブ関数が 1 バイト文字 (CharSet.Ansi) を使用している場合は、char バッファーの代わりに byte バッファーを使用できます。 次に例を示します。

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

パラメーターが入力としても使用されている場合は、null ターミネータが明示的に追加されている文字列データでバッファーを設定する必要があります。

どのようなときに警告を抑制するか

StringBuilder のマーシャリングによるパフォーマンスへの影響が懸念されない場合は、この規則違反を抑制します。

警告を抑制する

単一の違反を抑制するだけの場合は、ソース ファイルにプリプロセッサ ディレクティブを追加して無効にしてから、規則をもう一度有効にします。

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

ファイル、フォルダー、またはプロジェクトの規則を無効にするには、構成ファイルでその重要度を none に設定します。

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

詳細については、「コード分析の警告を抑制する方法」を参照してください。

こちらもご覧ください