Freigeben über


CA1045: Typen nicht als Verweis übergeben

TypeName

DoNotPassTypesByReference

CheckId

CA1045

Kategorie

Microsoft.Design

Unterbrechende Änderung

Breaking

Ursache

Eine öffentliche oder geschützte Methode in einem öffentlichen Typ verfügt über einen ref-Parameter, der einen primitiven Typ, einen Verweistyp oder einen Werttyp übernimmt, der kein integrierter Typ ist.

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 wird der Unterschied zwischen out-Parametern und ref-Parametern weithin nicht verstanden.

Wenn ein Verweistyp "als Verweis" übergeben wird, verwendet die Methode den Parameter, um eine andere Instanz des Objekts zurückzugeben. (Die Übergabe eines Verweistyps als Verweis wird auch Verwenden eines doppelten Zeigers [eines Zeigers auf einen Zeiger] oder doppelte Dereferenzierung genannt.) Bei Verwendung der Standardaufrufkonvention, also die Übergabe "als Wert", erhält ein Parameter, der einen Verweistyp übernimmt, bereits einen Zeiger auf das Objekt. Der Zeiger, nicht das Objekt, auf der er zeigt, wird als Wert übergeben. Die Übergabe als Wert bedeutet, dass die Methode den Zeiger zwar nicht dahingehend ändern kann, dass er auf eine neue Instanz des Verweistyps zeigt, aber den Inhalt des Objekts, auf den er zeigt, ändern kann. Bei den meisten Anwendungen ist dies ausreichend und ergibt das gewünschte Verhalten.

Wenn eine Methode eine andere Instanz zurückgeben muss, verwenden Sie dazu den Rückgabewert der Methode. Weitere Informationen zu verschiedenen Methoden, die Zeichenfolgen bearbeiten und eine neue Instanz einer Zeichenfolge zurückgeben, sind in der System.String-Klasse enthalten. Bei Verwendung dieses Modells wird die Entscheidung, ob das ursprüngliche Objekt beibehalten wird, dem Aufrufer überlassen.

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.

Tipp

Wenn Sie mit Parametern arbeiten, die umfangreiche Strukturen darstellen, können durch die zusätzlichen, zum Kopieren dieser Strukturen erforderlichen Ressourcen Leistungsbeeinträchtigungen verursacht werden, wenn die Übergabe als Wert erfolgt. In diesen Fällen sollten Sie den ref-Parameter oder den out-Parameter verwenden.

Behandeln von Verstößen

Um einen von einem Werttyp verursachten Verstoß gegen diese Regel zu beheben, muss die Methode das Objekt als Rückgabewert zurückgeben. Wenn die Methode mehrere Werte zurückgeben muss, muss sie so abgeändert werden, dass sie nur eine Instanz eines Objekts zurückgibt, in der die Werte gespeichert werden.

Zur Behebung eines von einem Verweistyp verursachten Verstoßes gegen diese Regel stellen Sie sicher, dass das gewünschte Verhalten darin besteht, eine neue Instanz des Verweises zurückzugeben. Wenn dem so ist, sollte die Methode ihren Rückgabewert zu diesem Zweck verwenden.

Wann sollten Warnungen unterdrückt werden?

Eine Warnung dieser Regel kann gefahrlos unterdrückt werden. Diese Konzeption kann jedoch Probleme hinsichtlich der Verwendbarkeit verursachen.

Beispiel

Die folgende Bibliothek enthält zwei Implementierungen einer Klasse, die Antworten auf das Feedback des Benutzers generiert. Die erste Implementierung (BadRefAndOut) zwingt den Bibliotheksbenutzer, drei Rückgabewerte zu verwalten. Die zweite Implementierung (RedesignedRefAndOut) vereinfacht die Handhabung für den Benutzer, indem eine Instanz einer Containerklasse (ReplyData) zurückgegeben wird, welche die Daten als eine Einheit verwaltet.

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 veranschaulicht die Verwendung aus der Sicht des Benutzers. Der Aufruf der umgestalteten Bibliothek (UseTheSimplifiedClass-Methode) ist einfacher, und die von der Methode zurückgegebenen Daten sind leicht zu handhaben. Die Ausgabe der beiden Methoden ist gleich.

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 Verweistypen 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:

  

Verwandte Regeln

CA1021: out-Parameter vermeiden