Udostępnij za pośrednictwem


CA1838: Unikaj StringBuilder parametrów dla parametrów P/Invoke

Właściwości Wartość
Identyfikator reguły CA1838
Tytuł Unikaj StringBuilder parametrów dla wywołań P/Invoke
Kategoria Wydajność
Poprawka powodująca niezgodność lub niezgodność Niezgodność
Domyślnie włączone na platformie .NET 9 Nie.

Przyczyna

P /Invoke ma StringBuilder parametr .

Opis reguły

Marshalling zawsze tworzy natywną kopię buforu StringBuilder , co powoduje wiele alokacji dla jednego wywołania P/Invoke. Aby przeprowadzić marshaling jako StringBuilder parametr P/Invoke, środowisko uruchomieniowe będzie:

  • Przydziel bufor macierzysty.
  • Jeśli jest In to parametr, skopiuj zawartość StringBuilder obiektu do buforu natywnego.
  • Jeśli jest Out to parametr, skopiuj bufor macierzysty do nowo przydzielonej tablicy zarządzanej.

Domyślnie StringBuilder wartość to In i Out.

Aby uzyskać więcej informacji na temat ciągów marshalingu, zobacz Domyślne marshalling dla ciągów.

Ta reguła jest domyślnie wyłączona, ponieważ może wymagać analizy wielkości liter po przypadku, czy naruszenie jest interesujące i potencjalnie nietrygalne refaktoryzacja w celu rozwiązania naruszenia. Użytkownicy mogą jawnie włączyć tę regułę, konfigurując jej ważność.

Jak naprawić naruszenia

Ogólnie rzecz biorąc, rozwiązanie naruszenia polega na przepracowaniu wywołań P/Invoke i jego obiektach wywołujących, aby użyć buforu zamiast StringBuilder. Specyfika będzie zależeć od przypadków użycia dla wywołania P/Invoke.

Oto przykład typowego scenariusza użycia StringBuilder jako buforu wyjściowego, który ma zostać wypełniony przez funkcję natywną:

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

W przypadku przypadków użycia, w których bufor jest mały, a unsafe kod jest akceptowalny, można użyć obiektu stackalloc do przydzielenia buforu na stosie:

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

W przypadku większych można przydzielić nową tablicę jako bufor:

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

Gdy wywołanie P/Invoke jest często wywoływane dla większych, ArrayPool<T> można użyć go, aby uniknąć powtarzających się alokacji i ciśnienia pamięci, które są z nimi dostarczane:

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

Jeśli rozmiar buforu nie jest znany do czasu wykonania, może być konieczne utworzenie buforu w inny sposób na podstawie rozmiaru, aby uniknąć przydzielania dużych za pomocą stackallocpolecenia .

W powyższych przykładach są używane znaki o szerokości 2 bajtów (CharSet.Unicode). Jeśli funkcja natywna używa 1-bajtowych znaków (CharSet.Ansi), byte można użyć buforu zamiast buforu char . Na przykład:

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

Jeśli parametr jest również używany jako dane wejściowe, muszą zostać wypełnione danymi ciągu z jawnie dodanym terminatorem o wartości null.

Kiedy pomijać ostrzeżenia

Pomiń naruszenie tej reguły, jeśli nie martwisz się o wpływ na wydajność marshalingu StringBuilder.

Pomijanie ostrzeżenia

Jeśli chcesz po prostu pominąć pojedyncze naruszenie, dodaj dyrektywy preprocesora do pliku źródłowego, aby wyłączyć, a następnie ponownie włączyć regułę.

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

Aby wyłączyć regułę dla pliku, folderu lub projektu, ustaw jego ważność na none w pliku konfiguracji.

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

Aby uzyskać więcej informacji, zobacz Jak pominąć ostrzeżenia dotyczące analizy kodu.

Zobacz też