CA1021: Evitar parâmetros out

Property Valor
ID da regra CA1021
Título Evitar parâmetros out
Categoria Projetar
Correção interruptiva ou sem interrupção Quebra
Habilitado por padrão no .NET 8 Não

Causa

Um método público ou protegido em um tipo público tem um parâmetro out.

Por padrão, essa regra apenas analisa os tipos visíveis externamente, mas isso é configurável.

Descrição da regra

A passagem de tipos por referência (usando out ou ref) requer experiência com ponteiros, compreensão de como os tipos de valor e tipos de referência diferem e manipulação de métodos com vários valores de retorno. Além disso, a diferença entre parâmetros out e ref não é amplamente compreendida.

Quando um tipo de referência é passado "por referência", o método pretende usar o parâmetro para retornar uma instância diferente do objeto. O ato de passar um tipo de referência por referência também é conhecido como usar um ponteiro duplo, ponteiro para um ponteiro ou dupla indireção. Ao usar a convenção de chamada padrão, que é passar "por valor", um parâmetro que recebe um tipo de referência já recebe um ponteiro para o objeto. O ponteiro, não o objeto para o qual ele aponta, é passado por valor. Passar por valor significa que o método não pode alterar o ponteiro para que ele aponte para uma nova instância do tipo de referência. No entanto, isso pode alterar o conteúdo do objeto para o qual ele aponta. Para a maioria dos aplicativos, isso é suficiente e gera o comportamento desejado.

Se um método deve retornar uma instância diferente, use o valor de retorno do método para fazer isso. Consulte a classe System.String para obter uma variedade de métodos que operam em cadeias de caracteres e retornam uma nova instância de uma cadeia de caracteres. Quando este modelo é usado, o chamador deve decidir se o objeto original é preservado.

Embora os valores de retorno sejam comuns e muito usados, a aplicação correta dos parâmetros out e ref requer habilidades intermediárias de design e codificação. Os arquitetos de bibliotecas com designs que visam um público geral não devem esperar que os usuários se tornem proficientes em trabalhar com parâmetros out ou ref.

Como corrigir violações

Para corrigir uma violação dessa regra causada por um tipo de valor, faça com que o método retorne o objeto como seu valor de retorno. Se o método deve retornar vários valores, redesenhe-o para retornar uma única instância de um objeto que contém os valores.

Para corrigir uma violação dessa regra causada por um tipo de referência, certifique-se de que o comportamento desejado seja retornar uma nova instância da referência. Se for, o método deve usar seu valor de retorno para fazer isso.

Quando suprimir avisos

É seguro suprimir um aviso desta regra. No entanto, esse design pode causar problemas de usabilidade.

Suprimir um aviso

Para suprimir apenas uma violação, adicione diretivas de pré-processador ao arquivo de origem a fim de desabilitar e, em seguida, reabilitar a regra.

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

Para desabilitar a regra em um arquivo, uma pasta ou um projeto, defina a severidade como none no arquivo de configuração.

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

Para obter mais informações, confira Como suprimir avisos de análise de código.

Configurar código para analisar

Use a opção a seguir para configurar em quais partes da base de código essa regra deve ser executada.

Você pode configurar essa opção apenas para essa regra, para todas as regras às quais ela se aplica ou para todas as regras nessa categoria (Design) às quais ela se aplica. Para saber mais, confira Opções de configuração de regra de qualidade de código.

Incluir superfícies de API específicas

É possível configurar em quais partes da base de código essa regra deverá ser executada, com base na acessibilidade. Por exemplo, para especificar que a regra deverá ser executada apenas na superfície de API não pública, adicione o seguinte par chave-valor a um arquivo .editorconfig no projeto:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Exemplo 1

A biblioteca a seguir mostra duas implementações de uma classe que gera respostas ao feedback do usuário. A primeira implementação (BadRefAndOut) força o usuário da biblioteca a gerenciar três valores retornados. A segunda implementação (RedesignedRefAndOut) simplifica a experiência do usuário retornando uma instância de uma classe de contêiner (ReplyData) que gerencia os dados como uma única unidade.

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)
    {
        bool returnReply = false;
        string replyText = "Your feedback has been forwarded " +
                           "to the product manager.";

        reply = String.Empty;
        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 class ReplyData
{
    bool _returnReply;

    // Constructors.
    public ReplyData()
    {
        this.Reply = String.Empty;
        this.Action = Actions.Discard;
        this._returnReply = false;
    }

    public ReplyData(Actions action, string reply, bool returnReply)
    {
        this.Reply = reply;
        this.Action = action;
        this._returnReply = returnReply;
    }

    // Properties.
    public string Reply { get; }
    public Actions Action { get; }

    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)
    {
        ReplyData answer;
        string replyText = "Your feedback has been forwarded " +
           "to the product manager.";

        switch (input)
        {
            case TypeOfFeedback.Complaint:
            case TypeOfFeedback.Praise:
                answer = new ReplyData(
                   Actions.ForwardToManagement,
                   "Thank you. " + replyText,
                   true);
                break;
            case TypeOfFeedback.Suggestion:
                answer = new ReplyData(
                   Actions.ForwardToDeveloper,
                   replyText,
                   true);
                break;
            case TypeOfFeedback.Incomprehensible:
            default:
                answer = new ReplyData();
                break;
        }
        return answer;
    }
}

Exemplo 2

O aplicativo a seguir ilustra a experiência do usuário. A chamada para a biblioteca reprojetada (método UseTheSimplifiedClass) é mais simples e as informações retornadas pelo método são facilmente gerenciadas. A saída dos dois métodos é idêntica.

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(typeof(TypeOfFeedback)))
        {
            // The call to the library.
            disposition[i] = BadRefAndOut.ReplyInformation(
               t, out reply[i], ref action[i]);
            Console.WriteLine("Reply: {0} Action: {1}  return? {2} ",
               reply[i], action[i], disposition[i]);
            i++;
        }
    }

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

    public static void UseClasses()
    {
        UseTheComplicatedClass();

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

        UseTheSimplifiedClass();
    }
}

Exemplo 3

A biblioteca de exemplo a seguir ilustra como os parâmetros ref para tipos de referência são usados e mostra uma maneira melhor de implementar essa funcionalidade.

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 += " ABCDE";
    }

    // The following syntax works, 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 += " ABCDE";
    }

    // The following syntax works 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";
    }
}

Exemplo 4

O aplicativo a seguir chama cada método na biblioteca para demonstrar o comportamento.

public class Test
{
    public static void MainTest()
    {
        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);
    }
}

Esse exemplo gera a saída a seguir:

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

Experimentar métodos de padrão

Os métodos que implementam o padrão Try<Something>, como System.Int32.TryParse, não geram essa violação. O exemplo a seguir mostra uma estrutura (tipo de valor) que implementa o método System.Int32.TryParse.

public struct Point
{
    public Point(int axisX, int axisY)
    {
        X = axisX;
        Y = axisY;
    }

    public int X { get; }

    public int Y { get; }

    public override int GetHashCode()
    {
        return X ^ Y;
    }

    public override bool Equals(object? obj)
    {
        if (!(obj is Point))
            return false;

        return Equals((Point)obj);
    }

    public bool Equals(Point other)
    {
        if (X != other.X)
            return false;

        return Y == other.Y;
    }

    public static bool operator ==(Point point1, Point point2)
    {
        return point1.Equals(point2);
    }

    public static bool operator !=(Point point1, Point point2)
    {
        return !point1.Equals(point2);
    }

    // Does not violate this rule
    public static bool TryParse(string value, out Point result)
    {
        // TryParse Implementation
        result = new Point(0, 0);
        return false;
    }
}

CA1045: Não passar tipos por referência