分享方式:


CA1838:避免 StringBuilder P/Invokes 的參數

屬性
規則識別碼 CA1838
標題 避免 StringBuilder P/Invokes 的參數
類別 效能
修正程式是中斷或非中斷 不中斷
預設在 .NET 8 中啟用 No

原因

P/Invoke 具有 StringBuilder 參數。

檔案描述

StringBuilder的封送處理一律會建立原生緩衝區複本,導致一個 P/Invoke 呼叫有多個配置。 若要封送處理 StringBuilder 為 P/Invoke 參數,運行時間會:

  • 配置原生緩衝區。
  • In如果是參數,請將 的內容StringBuilder複製到原生緩衝區。
  • Out如果是參數,請將原生緩衝區複製到新配置的Managed陣列。

預設為 StringBuilderInOut

如需封送處理字串的詳細資訊,請參閱 字串的預設封送處理。

默認會停用此規則,因為可能需要逐一案例分析違規是否感興趣,而且可能不簡單重構以處理違規。 用戶可以藉由 設定其嚴重性來明確啟用此規則。

如何修正違規

一般而言,解決違規涉及重新處理 P/Invoke 及其呼叫者以使用緩衝區,而不是 StringBuilder。 細節取決於 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), byte 則可以使用緩衝區,而不是 char 緩衝區。 例如:

[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

如需詳細資訊,請參閱 如何隱藏程式代碼分析警告

另請參閱