CA1045: Typen nicht als Verweis übergeben.

Eigenschaft Wert
Regel-ID CA1045
Titel Typen nicht als Verweis übergeben.
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 in einem öffentlichen Typ verfügt über einen ref-Parameter, der einen einfachen Typ, einen Verweistyp oder einen Werttyp annimmt, bei dem es sich nicht um einen der integrierten Typen handelt.

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. (Die Übergabe eines Verweistyps als Verweis wird auch als Verwendung eines doppelten Zeigers, Zeiger auf einen Zeiger oder doppelte Dereferenzierung bezeichnet.) Mit der Standardaufruf-Konvention, die als Wert übergeben wird, erhält ein Parameter, der einen Verweistyp annimmt, bereits einen Zeiger auf das Objekt. Der Zeiger wird als Wert übermittelt (nicht das Objekt, auf das der Zeiger verweist). Die Übergabe nach Wert bedeutet, dass die Methode den Zeiger nicht ändern kann, damit Sie auf eine neue Instanz des Verweistyps verweist, kann jedoch den Inhalt des Objekts ändern, auf das es verweist. Bei den meisten Anwendungen ist dies ausreichend und führt zum Verhalten, das Sie wünschen.

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. Wenn dieses Modell verwendet wird, wird es dem Aufrufer überlassen, zu entscheiden, ob das ursprüngliche Objekt beibehalten wird.

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.

Hinweis

Wenn Sie mit Parametern arbeiten, die große Strukturen darstellen, können die zusätzlichen Ressourcen, die erforderlich sind, um diese Strukturen zu kopieren, bei der Übergabe nach Wert einen Leistungseffekt verursachen. In diesen Fällen können Sie die Verwendung der ref- oder out-Parameter in Erwägung ziehen.

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 Ihr 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?

Es ist sicher, eine Warnung aus dieser Regel zu unterdrücken. Dieser Entwurf kann jedoch Probleme mit der Benutzerfreundlichkeit verursachen.

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 CA1045
// The code that's violating the rule is on this line.
#pragma warning restore CA1045

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.CA1045.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 das Feedback des Benutzers 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
{
    string reply;
    Actions action;
    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 { return reply; } }
    public Actions Action { get { return action; } }

    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 Main1045()
    {
        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 = 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";
    }
}

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

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

CA1021: out-Parameter vermeiden.