CA1021: out-Parameter vermeiden.

Eigenschaft Wert
Regel-ID CA1021
Titel out-Parameter vermeiden.
Kategorie Design
Fix führt oder führt nicht zur Unterbrechung Breaking
Standardmäßig in .NET 8 aktiviert Nein

Ursache

Eine öffentliche oder geschützte Methode unter einem öffentlichen Typ verfügt über einen out-Parameter.

Standardmäßig werden mit dieser Regel nur extern sichtbare Typen überprüft, aber dies ist konfigurierbar.

Regelbeschreibung

Die Übergabe von Typen als Verweis (mit out oder ref) erfordert Erfahrung im Umgang mit Zeigern, Kenntnisse der Unterschiede zwischen Wert- und Verweistypen und Erfahrung im Umgang mit Methoden mit mehreren Rückgabewerten. Auch mit dem Unterschied zwischen out- und ref-Parametern ist nicht jeder vertraut.

Wenn ein Verweistyp „durch Verweis“ übergeben wird, beabsichtigt die Methode, den Parameter für die Rückgabe einer anderen Instanz des Objekts zu nutzen. Das Übergeben eines Verweistyps „durch Verweis“ wird auch als Verwendung eines doppelten Zeigers, „Zeiger auf einen Zeiger“ oder „Doppelte Dereferenzierung“ bezeichnet. Indem die Standardaufrufkonvention verwendet wird (also „als Wert“), erhält ein Parameter, von dem ein Verweistyp verwendet wird, bereits einen Zeiger auf das Objekt. Der Zeiger wird als Wert übermittelt (nicht das Objekt, auf das der Zeiger verweist). Das Übergeben „als Wert“ bedeutet, dass der Zeiger von der Methode nicht so geändert werden kann, dass er auf eine neue Instanz des Verweistyps verweist. Es kann aber der Inhalt des Objekts geändert werden, auf das verwiesen wird. Bei den meisten Anwendungen ist dies ausreichend und führt zum gewünschten Verhalten.

Wenn eine Methode eine andere Instanz zurückgeben muss, sollten Sie den Rückgabewert der Methode verwenden, um dies zu erreichen. Auf der Seite zur System.String-Klasse finden Sie verschiedene Methoden, die auf Zeichenfolgen basieren und eine neue Instanz einer Zeichenfolge zurückgeben. Bei Verwendung dieses Modells muss vom Aufrufer die Entscheidung getroffen werden, ob das ursprüngliche Objekt beibehalten werden soll.

Rückgabewerte sind zwar gängig und werden häufig genutzt, aber für die richtige Anwendung der Parameter out und ref werden bestimmte Entwurfs- und Codierungskenntnisse benötigt. Bibliotheksarchitekten, die für eine allgemeine Zielgruppe entwerfen, sollten nicht erwarten, dass Benutzer das Arbeiten mit out- oder ref-Parametern beherrschen.

Behandeln von Verstößen

Zum Behandeln eines Verstoßes gegen diese Regel, der durch einen Werttyp verursacht wird, sollten Sie für die Methode festlegen, dass das Objekt als ihr eigener Rückgabewert zurückgegeben wird. Falls von der Methode mehrere Werte zurückgegeben werden müssen, sollten Sie diese so neu entwerfen, dass eine einzelne Instanz eines Objekts mit den Werten zurückgegeben wird.

Zum Behandeln eines Verstoßes gegen diese Regel, der durch einen Verweistyp verursacht wird, sollten Sie sicherstellen, dass als gewünschtes Verhalten eine neue Instanz des Verweises zurückgegeben wird. In diesem Fall sollte von der Methode hierfür ihr eigener Rückgabewert genutzt werden.

Wann sollten Warnungen unterdrückt werden?

Eine Warnung, die auf dieser Regel basiert, kann problemlos unterdrückt werden. Dieser Entwurf kann aber zu Problemen mit der Benutzerfreundlichkeit führen.

Unterdrücken einer Warnung

Um nur eine einzelne Verletzung zu unterdrücken, fügen Sie der Quelldatei Präprozessoranweisungen hinzu, um die Regel zu deaktivieren und dann wieder zu aktivieren.

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

Um die Regel für eine Datei, einen Ordner oder ein Projekt zu deaktivieren, legen Sie den Schweregrad in der Konfigurationsdatei auf none fest.

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

Weitere Informationen finden Sie unter Vorgehensweise: Unterdrücken von Codeanalyse-Warnungen.

Konfigurieren des zu analysierenden Codes

Mithilfe der folgenden Option können Sie konfigurieren, für welche Teile Ihrer Codebasis diese Regel ausgeführt werden soll.

Sie können diese Optionen nur für diese Regel, für alle zutreffenden Regeln oder für alle zutreffenden Regeln in dieser Kategorie (Entwurf) konfigurieren. Weitere Informationen finden Sie unter Konfigurationsoptionen für die Codequalitätsregel.

Einschließen bestimmter API-Oberflächen

Sie können je nach Zugänglichkeit festlegen, für welche Bestandteile Ihrer Codebasis diese Regel ausgeführt wird. Sie können beispielsweise festlegen, dass die Regel nur für die nicht öffentliche API-Oberfläche ausgeführt werden soll, indem Sie einer EDITORCONFIG-Datei in Ihrem Projekt das folgende Schlüssel-Wert-Paar hinzufügen:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Beispiel 1

In der folgenden Bibliothek sind zwei Implementierungen einer Klasse dargestellt, die Antworten auf Benutzerfeedback generiert. Bei der ersten Implementierung (BadRefAndOut) wird der Benutzer der Bibliothek gezwungen, drei Rückgabewerte zu verwalten. Mit der zweiten Implementierung (RedesignedRefAndOut) wird der Ablauf für den Benutzer vereinfacht, indem eine Instanz einer Containerklasse (ReplyData) zurückgegeben wird, mit der die Daten als Einheit verwaltet werden.

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

Beispiel 2

Die folgende Anwendung veranschaulicht den Ablauf für den Benutzer. Der Aufruf der neu entworfenen Bibliothek (UseTheSimplifiedClass-Methode) ist einfacher, und die von der Methode zurückgegebenen Informationen können leicht verwaltet werden. Die Ausgabe der beiden Methoden ist jeweils identisch.

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

Beispiel 3

Mit der folgenden Beispielbibliothek wird veranschaulicht, wie ref-Parameter für Verweistypen verwendet werden, und sie verdeutlicht eine bessere Möglichkeit zur Implementierung dieser Funktionalität.

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

Beispiel 4

Mit der folgenden Anwendung werden die einzelnen Methoden in der Bibliothek aufgerufen, um das Verhalten zu demonstrieren.

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

Dieses Beispiel erzeugt die folgende Ausgabe:

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

Methoden mit Try-Muster

Für Methoden, bei denen das Try<Something>-Muster implementiert wird (z. B. System.Int32.TryParse), kommt es nicht zu diesem Verstoß. Im folgenden Beispiel wird eine Struktur (Werttyp) veranschaulicht, von der die System.Int32.TryParse-Methode implementiert wird.

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: Typen nicht als Verweis übergeben.