Condividi tramite


CA1021: Evitare parametri out

Proprietà valore
ID regola CA1021
Titolo Evitare parametri out
Categoria Progettazione
Correzione che causa un'interruzione o un'interruzione Interruzione
Abilitato per impostazione predefinita in .NET 8 No

Causa

Un metodo pubblico o protetto in un tipo pubblico ha un out parametro .

Per impostazione predefinita, questa regola esamina solo i tipi visibili esternamente, ma è configurabile.

Descrizione regola

Il passaggio di tipi per riferimento (usando out o ref) richiede esperienza con i puntatori, comprendere in che modo i tipi di valore e i tipi di riferimento differiscono e gestiscono i metodi con più valori restituiti. Inoltre, la differenza tra out parametri e ref non è ampiamente comprensibile.

Quando viene passato un tipo di riferimento "per riferimento", il metodo intende utilizzare il parametro per restituire un'istanza diversa dell'oggetto. Il passaggio di un tipo riferimento per riferimento è noto anche come uso di un puntatore doppio, un puntatore a un puntatore o un doppio riferimento indiretto. Usando la convenzione di chiamata predefinita, che passa "per valore", un parametro che accetta un tipo riferimento riceve già un puntatore all'oggetto. Il puntatore, non l'oggetto a cui punta, viene passato per valore. Passare per valore significa che il metodo non può modificare il puntatore in modo che punti a una nuova istanza del tipo di riferimento. Tuttavia, può modificare il contenuto dell'oggetto a cui punta. Per la maggior parte delle applicazioni è sufficiente e restituisce il comportamento desiderato.

Se un metodo deve restituire un'istanza diversa, utilizzare il valore restituito del metodo per eseguire questa operazione. Vedere la System.String classe per un'ampia gamma di metodi che operano su stringhe e restituiscono una nuova istanza di una stringa. Quando si utilizza questo modello, il chiamante deve decidere se l'oggetto originale viene mantenuto.

Sebbene i valori restituiti siano comuni e usati in modo predefinito, l'applicazione corretta di out parametri e ref richiede competenze di progettazione e codifica intermedie. Gli architetti della libreria che progettano per un pubblico generale non devono aspettarsi che gli utenti diventino esperti nell'uso di out parametri o ref .

Come correggere le violazioni

Per correggere una violazione di questa regola causata da un tipo di valore, fare in modo che il metodo restituisca l'oggetto come valore restituito. Se il metodo deve restituire più valori, riprogettarlo per restituire una singola istanza di un oggetto che contiene i valori.

Per correggere una violazione di questa regola causata da un tipo riferimento, assicurarsi che il comportamento desiderato restituisca una nuova istanza del riferimento. In caso affermativo, il metodo deve usare il relativo valore restituito per eseguire questa operazione.

Quando eliminare gli avvisi

È sicuro eliminare un avviso da questa regola. Tuttavia, questa progettazione potrebbe causare problemi di usabilità.

Eliminare un avviso

Se si vuole eliminare una singola violazione, aggiungere direttive del preprocessore al file di origine per disabilitare e quindi riabilitare la regola.

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

Per disabilitare la regola per un file, una cartella o un progetto, impostarne la gravità none su nel file di configurazione.

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

Per altre informazioni, vedere Come eliminare gli avvisi di analisi del codice.

Configurare il codice da analizzare

Usare l'opzione seguente per configurare le parti della codebase in cui eseguire questa regola.

È possibile configurare questa opzione solo per questa regola, per tutte le regole a cui si applica o per tutte le regole in questa categoria (Progettazione) a cui si applica. Per altre informazioni, vedere Opzioni di configurazione delle regole di qualità del codice.

Includere superfici API specifiche

È possibile configurare le parti della codebase in modo da eseguire questa regola in base all'accessibilità. Ad esempio, per specificare che la regola deve essere eseguita solo sulla superficie dell'API non pubblica, aggiungere la coppia chiave-valore seguente a un file con estensione editorconfig nel progetto:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Esempio 1

La libreria seguente illustra due implementazioni di una classe che genera risposte ai commenti e suggerimenti degli utenti. La prima implementazione (BadRefAndOut) impone all'utente della libreria di gestire tre valori restituiti. La seconda implementazione (RedesignedRefAndOut) semplifica l'esperienza utente restituendo un'istanza di una classe contenitore (ReplyData) che gestisce i dati come singola unità.

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

Esempio 2

L'applicazione seguente illustra l'esperienza dell'utente. La chiamata alla libreria riprogettata (UseTheSimplifiedClass metodo ) è più semplice e le informazioni restituite dal metodo sono facilmente gestite. L'output dei due metodi è identico.

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

Esempio 3

Nella libreria di esempio seguente viene illustrato come ref vengono usati i parametri per i tipi di riferimento e viene illustrato un modo migliore per implementare questa funzionalità.

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";
    }
}

Esempio 4

L'applicazione seguente chiama ogni metodo nella libreria per illustrare il 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);
    }
}

Nell'esempio viene prodotto l'output seguente:

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

Provare i metodi del modello

I metodi che implementano il modello Try<Something> , ad esempio System.Int32.TryParse, non generano questa violazione. Nell'esempio seguente viene illustrata una struttura (tipo valore) che implementa il System.Int32.TryParse metodo .

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: Non passare i tipi per riferimento