Бөлісу құралы:


CA1045: не передавайте типы по ссылке

Свойство Значение
Идентификатор правила CA1045
Заголовок Не передавайте типы по ссылке
Категория Проектирование
Исправление является критическим или не критическим Срочное
Включен по умолчанию в .NET 10 Нет
Применимые языки C# и Visual Basic

Причина

Открытый или защищенный метод в открытом типе имеет параметр ref, принимающий примитивный тип, ссылочный тип или тип значения, не являющийся одним из встроенных типов.

Описание правила

Для реализации передачи типов по ссылке (с помощью ключевого слова out или ref) от разработчика требуется опыт работы с указателями, понимание отличия между типами значения и ссылочными типами и умение работать с методами с несколькими возвращаемыми значениями. Кроме того, далеко не все понимают разницу между параметрами out и ref.

Когда ссылочный тип передается "по ссылке", метод намеревается использовать этот параметр для возврата другого экземпляра объекта. (Передача ссылочного типа по ссылке также известна как использование двойного указателя, указателя на указатель или двойной индирекции.) По умолчанию используется соглашение о вызовах, которое передает данные "по значению", и параметру, принимающему ссылочный тип, уже передается указатель на объект. Указатель, а не объект, на который он указывает, передается по значению. Передача по значению означает, что метод не может изменить указатель, чтобы он указывал на новый экземпляр ссылочного типа, но может изменить содержимое объекта, на который он указывает. Для большинства приложений это достаточно и дает желаемое поведение.

Если метод должен возвращать другой экземпляр, используйте для этого возвращаемое значение метода. Методы, работающие с строками и возвращающие новый экземпляр строки, см. в System.String классе. Использование этой модели позволяет вызывающей стороне решить, сохраняется ли исходный объект.

Хотя возвращаемые значения являются наиболее распространенными и часто используются, правильное применение параметров out и ref требует среднего уровня навыков проектирования и программирования. Архитекторам, разрабатывающим библиотеки для широкого использования, не следует рассчитывать, что пользователи отлично разбираются в использовании параметров out и ref.

Примечание.

Работая с большими структурами параметров, дополнительные ресурсы, необходимые для их копирования, могут сказаться на производительности при передаче по значению. В таких случаях можно рассмотреть использование параметров ref или out.

Устранение нарушений

Чтобы устранить нарушение этого правила, вызванного типом значения, метод возвращает объект в качестве возвращаемого значения. Если метод должен возвращать несколько значений, перепроектируйте его для возврата одного экземпляра объекта, содержащего значения.

Чтобы исправить нарушение этого правила, вызванное ссылочным типом, убедитесь, что желаемое поведение заключается в возврате нового экземпляра этого типа. Если это так, метод должен использовать его возвращаемое значение для этого.

Когда лучше отключить предупреждения

Предупреждения о нарушении этого правила можно безопасно скрывать. Однако такая схема может вызвать проблемы с удобством использования.

Отключение предупреждений

Если вы просто хотите отключить одно нарушение, добавьте директивы препроцессора в исходный файл, чтобы отключить и повторно включить правило.

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

Чтобы отключить правило для файла, папки или проекта, задайте его серьезность none в файле конфигурации.

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

Дополнительные сведения см. в разделе Практическое руководство. Скрытие предупреждений анализа кода.

Настройка кода для анализа

Используйте следующий параметр, чтобы выбрать части базы кода для применения этого правила.

Этот параметр можно настроить только для этого правила, для всех правил, к которым он применяется, или для всех правил в этой категории (конструкторе), к которым она применяется. Дополнительные сведения см. в статье Параметры конфигурации правила качества кода.

Включите конкретные поверхности API

Вы можете настроить компоненты базы кода для выполнения этого правила на основе их специальных возможностей, задав параметр api_surface. Например, чтобы указать, что правило должно выполняться только для непубличной поверхности API, добавьте следующую пару "ключ-значение" в файл .editorconfig в ваш проект:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Примечание.

Замените XXXX частью CAXXXX идентификатором применимого правила.

Пример 1

В следующей библиотеке показаны две реализации класса, которые создают ответы на отзывы пользователя. Первая реализация (BadRefAndOut) заставляет пользователя библиотеки управлять тремя возвращаемыми значениями. Вторая реализация (RedesignedRefAndOut) упрощает взаимодействие с пользователем, возвращая экземпляр класса контейнера (ReplyData), который управляет данными как единым целым.

public enum Actions
{
    Unknown,
    Discard,
    ForwardToManagement,
    ForwardToDeveloper
}

public enum TypeOfFeedback
{
    Complaint,
    Praise,
    Suggestion,
    Incomprehensible
}

public class BadRefAndOut
{
    // Violates rule: DoNotPassTypesByReference.

    public static bool ReplyInformation(TypeOfFeedback input,
       out string reply, ref Actions action)
    {
        string replyText = """
            Your feedback has been forwarded to the product manager.
            """;

        reply = string.Empty;
        bool returnReply;
        switch (input)
        {
            case TypeOfFeedback.Complaint:
            case TypeOfFeedback.Praise:
                action = Actions.ForwardToManagement;
                reply = "Thank you. " + replyText;
                returnReply = true;
                break;
            case TypeOfFeedback.Suggestion:
                action = Actions.ForwardToDeveloper;
                reply = replyText;
                returnReply = true;
                break;
            case TypeOfFeedback.Incomprehensible:
            default:
                action = Actions.Discard;
                returnReply = false;
                break;
        }
        return returnReply;
    }
}

// Redesigned version does not use out or ref parameters;
// instead, it returns this container type.

public record class ReplyData(string Reply, Actions Action, bool ReturnReply = false)
{
    public override string ToString()
    {
        return string.Format("Reply: {0} Action: {1} return? {2}",
           Reply, Action.ToString(), ReturnReply.ToString());
    }
}

public class RedesignedRefAndOut
{
    public static ReplyData? ReplyInformation(TypeOfFeedback input)
    {
        string replyText = "Your feedback has been forwarded " +
           "to the product manager.";
        ReplyData? answer = input switch
        {
            TypeOfFeedback.Complaint or TypeOfFeedback.Praise => new ReplyData(
                                   "Thank you. " + replyText,
                                   Actions.ForwardToManagement,
                                   true),
            TypeOfFeedback.Suggestion => new ReplyData(
                                   replyText,
                                   Actions.ForwardToDeveloper,
                                   true),
            _ => null,
        };
        return answer;
    }
}

Пример 2

Следующее приложение иллюстрирует опыт пользователя. Вызов переработанной библиотеки (метода UseTheSimplifiedClass) более прост, и сведениями, возвращаемыми методом, легко управлять. Оба метода дают одинаковые результаты.

public class UseComplexMethod
{
    static void UseTheComplicatedClass()
    {
        // Using the version with the ref and out parameters. 
        // You do not have to initialize an out parameter.

        string[] reply = new string[5];

        // You must initialize a ref parameter.
        Actions[] action = [Actions.Unknown,Actions.Unknown,
                         Actions.Unknown,Actions.Unknown,
                         Actions.Unknown,Actions.Unknown];
        bool[] disposition = new bool[5];
        int i = 0;

        foreach (TypeOfFeedback t in Enum.GetValues<TypeOfFeedback>())
        {
            // The call to the library.
            disposition[i] = BadRefAndOut.ReplyInformation(
               t, out reply[i], ref action[i]);
            Console.WriteLine($"Reply: {reply[i]} Action: {action[i]}  return? {disposition[i]} ");
            i++;
        }
    }

    static void UseTheSimplifiedClass()
    {
        ReplyData[] answer = new ReplyData[5];
        int i = 0;
        foreach (TypeOfFeedback t in Enum.GetValues<TypeOfFeedback>())
        {
            // The call to the library.
            answer[i] = RedesignedRefAndOut.ReplyInformation(t);
            Console.WriteLine(answer[i++]);
        }
    }

    public static void Main1045()
    {
        UseTheComplicatedClass();

        // Print a blank line in output.
        Console.WriteLine("");

        UseTheSimplifiedClass();
    }
}

Пример 3

В следующем примере библиотеки показано, как используются параметры ref для ссылочных типов, и демонстрируется лучший способ реализации этой функции.

public class ReferenceTypesAndParameters
{
    // The following syntax will not work. You cannot make a
    // reference type that is passed by value point to a new
    // instance. This needs the ref keyword.

    public static void BadPassTheObject(string argument)
    {
        argument = argument + " ABCDE";
    }

    // The following syntax will work, but is considered bad design.
    // It reassigns the argument to point to a new instance of string.
    // Violates rule DoNotPassTypesByReference.

    public static void PassTheReference(ref string argument)
    {
        argument = argument + " ABCDE";
    }

    // The following syntax will work and is a better design.
    // It returns the altered argument as a new instance of string.

    public static string BetterThanPassTheReference(string argument)
    {
        return argument + " ABCDE";
    }
}

Пример 4

Следующее приложение вызывает каждый метод в библиотеке, чтобы продемонстрировать поведение.

public class Test
{
    public static void Main1045()
    {
        string s1 = "12345";
        string s2 = "12345";
        string s3 = "12345";

        Console.WriteLine("Changing pointer - passed by value:");
        Console.WriteLine(s1);
        ReferenceTypesAndParameters.BadPassTheObject(s1);
        Console.WriteLine(s1);

        Console.WriteLine("Changing pointer - passed by reference:");
        Console.WriteLine(s2);
        ReferenceTypesAndParameters.PassTheReference(ref s2);
        Console.WriteLine(s2);

        Console.WriteLine("Passing by return value:");
        s3 = ReferenceTypesAndParameters.BetterThanPassTheReference(s3);
        Console.WriteLine(s3);
    }
}

В примере получается следующий вывод.

Changing pointer - passed by value:
12345
12345
Changing pointer - passed by reference:
12345
12345 ABCDE
Passing by return value:
12345 ABCDE

CA1021: не используйте параметры out