CA1838: Avoid StringBuilder
parameters for P/Invokes
Property | Value |
---|---|
Rule ID | CA1838 |
Title | Avoid StringBuilder parameters for P/Invokes |
Category | Performance |
Fix is breaking or non-breaking | Non-breaking |
Enabled by default in .NET 8 | No |
Cause
A P/Invoke has a StringBuilder parameter.
Rule description
Marshalling of StringBuilder
always creates a native buffer copy, resulting in multiple allocations for one P/Invoke call. To marshal a StringBuilder
as a P/Invoke parameter, the runtime will:
- Allocate a native buffer.
- If it is an
In
parameter, copy the contents of theStringBuilder
to the native buffer. - If it is an
Out
parameter, copy the native buffer into a newly allocated managed array.
By default, StringBuilder
is In
and Out
.
For more information about marshalling strings, see Default marshalling for strings.
This rule is disabled by default, because it can require case-by-case analysis of whether the violation is of interest and potentially non-trivial refactoring to address the violation. Users can explicitly enable this rule by configuring its severity.
How to fix violations
In general, addressing a violation involves reworking the P/Invoke and its callers to use a buffer instead of StringBuilder
. The specifics would depend on the use cases for the P/Invoke.
Here is an example for the common scenario of using StringBuilder
as an output buffer to be filled by the native function:
// 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();
}
For use cases where the buffer is small and unsafe
code is acceptable, stackalloc can be used to allocate the buffer on the 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);
}
}
For larger buffers, a new array can be allocated as the 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);
}
When the P/Invoke is frequently called for larger buffers, ArrayPool<T> can be used to avoid the repeated allocations and memory pressure that comes with them:
[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);
}
}
If the buffer size is not known until runtime, the buffer may need to be created differently based on the size to avoid allocating large buffers with stackalloc
.
The preceding examples use 2-byte wide characters (CharSet.Unicode
). If the native function uses 1-byte characters (CharSet.Ansi
), a byte
buffer can be used instead of a char
buffer. For example:
[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);
}
}
If the parameter is also used as input, the buffers need to be populated with the string data with any null terminator explicitly added.
When to suppress warnings
Suppress a violation of this rule if you're not concerned about the performance impact of marshalling a StringBuilder
.
Suppress a warning
If you just want to suppress a single violation, add preprocessor directives to your source file to disable and then re-enable the rule.
#pragma warning disable CA1838
// The code that's violating the rule is on this line.
#pragma warning restore CA1838
To disable the rule for a file, folder, or project, set its severity to none
in the configuration file.
[*.{cs,vb}]
dotnet_diagnostic.CA1838.severity = none
For more information, see How to suppress code analysis warnings.
See also
Phản hồi
https://aka.ms/ContentUserFeedback.
Sắp ra mắt: Trong năm 2024, chúng tôi sẽ dần gỡ bỏ Sự cố với GitHub dưới dạng cơ chế phản hồi cho nội dung và thay thế bằng hệ thống phản hồi mới. Để biết thêm thông tin, hãy xem:Gửi và xem ý kiến phản hồi dành cho