Share via


out-Parameter vermeiden

Aktualisiert: November 2007

     TypeName

AvoidOutParameters

CheckId

CA1021

Kategorie

Microsoft.Design

Unterbrechende Änderung

Breaking

Ursache

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

Regelbeschreibung

Die Übergabe von Typen als Verweis (mithilfe von out oder ref) erfordert Erfahrung mit Zeigern, die Kenntnis der Unterschiede zwischen Werttypen und Referenztypen sowie die Behandlung von Methoden mit mehreren Rückgabewerten. Vielen Benutzern ist auch der Unterschied zwischen dem out-Parameter und dem ref-Parameter nicht klar.

Wenn ein Referenztyp "als Verweis" übergeben wird, beabsichtigt die Methode, den Parameter zur Rückgabe einer anderen Instanz des Objekts zu verwenden. Die Übergabe eines Referenztyps als Verweis wird auch als Verwendung eines Doppelzeigers, als Verwendung eines Zeigers auf einen Zeiger oder als doppelte Dereferenzierung bezeichnet. Bei Verwendung der Standardaufrufkonvention, also der Übergabe "als Wert", erhält ein Parameter, der einen Referenztyp übernimmt, bereits einen Zeiger auf das Objekt. Der Zeiger, nicht das Objekt, auf das dieser zeigt, wird als Wert übergeben. Die Übergabe als Wert bedeutet, dass die Methode den Zeiger nicht dahingehend ändern kann, dass er auf eine neue Instanz des Referenztyps zeigt. Der Inhalt des Objekts, auf den er zeigt, kann jedoch geändert werden. Bei den meisten Anwendungen reicht dies aus, um das gewünschte Verhalten zu erzielen.

Wenn eine Methode eine andere Instanz zurückgeben muss, verwenden Sie dazu den Rückgabewert der Methode. Die System.String-Klasse enthält verschiedene Methoden, mit denen Zeichenfolgen bearbeitet werden und die eine neue Instanz einer Zeichenfolge zurückgeben. Bei Verwendung dieses Modells muss der Aufrufer entscheiden, ob das ursprüngliche Objekt erhalten bleiben soll.

Obwohl Rückgabewerte häufig vorkommen und verwendet werden, erfordert die Anwendung von out-Parametern und ref-Parametern mittlere Design- und Programmierkenntnisse. Entwickler von Bibliotheken für Durchschnittsbenutzer sollten nicht davon ausgehen, dass die Benutzer den out-Parameter oder den ref-Parameter richtig verwenden können.

Behandlung von Verstößen

Um einen durch einen Werttyp verursachten Verstoß gegen diese Regel zu beheben, lassen Sie das Objekt von der Methode als Rückgabewert zurückgeben. Wenn die Methode mehrere Rückgabewerte zurückgeben muss, entwerfen Sie sie erneut, sodass sie eine einzelne Instanz eines Objekts zurückgibt, das die Werte enthält.

Um einen durch einen Referenztyp verursachten Verstoß gegen diese Regel zu beheben, stellen Sie sicher, dass die Rückgabe einer neuen Instanz des Verweises das gewünschte Verhalten ist. Ist dies der Fall, sollte die Methode dazu ihren Rückgabewert verwenden.

Wann sollten Warnungen unterdrückt werden?

Warnungen dieser Regel können gefahrlos unterdrückt werden. Dieser Entwurf kann jedoch Probleme hinsichtlich der Verwendbarkeit verursachen.

Beispiel

Die folgende Bibliothek zeigt zwei Implementierungen einer Klasse, die Reaktionen auf das Feedback des Benutzers generiert. Die erste Implementierung (BadRefAndOut) zwingt den Benutzer der Bibliothek, drei Rückgabewerte zu verwalten. Die zweite Implementierung (RedesignedRefAndOut) ist benutzerfreundlicher, da eine Instanz einer Containerklasse (ReplyData) zurückgegeben wird, mit der die Daten in einer einzelnen Einheit verwaltet werden.

using System;

namespace DesignLibrary
{
   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;
      }
   }
}

Die folgende Anwendung verdeutlicht die Benutzerumgebung. Der Aufruf der neu konzipierten Bibliothek (UseTheSimplifiedClass-Methode) ist unkomplizierter, und die durch die Methode zurückgegebenen Informationen lassen sich problemlos verwalten. Die Ausgabe der beiden Methoden ist identisch.

using System;

namespace DesignLibrary
{
   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 Main()
      {
         UseTheComplicatedClass();

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

         UseTheSimplifiedClass();
      }
   }
}

Anhand der folgenden Beispielbibliothek wird verdeutlicht, wie ref-Parameter für Referenztypen verwendet werden, und wie diese Funktionalität besser implementiert werden kann.

using System;

namespace DesignLibrary
{
   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";
      }
   }
}

Die folgende Anwendung ruft jede Methode in der Bibliothek auf, um das Verhalten zu veranschaulichen.

using System;

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

Folgende Ergebnisse werden zurückgegeben:

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

Methoden nach dem Try-Muster

Beschreibung

Methoden, durch die das Muster Try<Something> implementiert wird, z. B. Int32TryParse(), lösen einen solchen Verstoß nicht aus. Im folgenden Beispiel wird eine Struktur (Werttyp) veranschaulicht, durch die die Int32TryParse()-Methode implementiert wird.

Code

using System;

namespace Samples
{
    public struct Point
    {
        private readonly int _X;
        private readonly int _Y;

        public Point(int axisX, int axisY)
        {
            _X = axisX;
            _Y = axisY;
        }

        public int X
        {
            get { return _X; }
        }

        public int Y
        {
            get { return _Y; }
        }

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

Verwandte Regeln

Typen nicht als Verweis übergeben