CA1021 : Éviter les paramètres out

TypeName

AvoidOutParameters

CheckId

CA1021

Catégorie

Microsoft.CSharp

Modification avec rupture

Oui

Cause

Une méthode publique ou protégée dans un type public dispose d'un paramètre out.

Description de la règle

Passer des types par référence (en utilisant out ou ref) nécessite une certaine expérience des pointeurs, de comprendre la différence entre les types valeur et les types référence, ainsi que la gestion de méthodes impliquant plusieurs valeurs de retour. Par ailleurs, la différence entre les paramètres out et refest généralement peu comprise.

Lorsqu'un type référence est passé "par référence", la méthode projette d'utiliser le paramètre pour retourner une instance différente de l'objet. Passer un type référence par référence est également connu comme l'utilisation d'un pointeur double, d'un pointeur vers un pointeur, ou d'une indirection double. En utilisation la convention d'appel par défaut, qui revient à passer "par valeur", un paramètre qui accepte un type référence reçoit déjà un pointeur vers l'objet. Le pointeur, et non l'objet sur lequel il pointe, est passé par valeur. Passer par valeur signifie que la méthode ne peut pas modifier le pointeur afin que celui-ci pointe vers une nouvelle instance du type référence. Toutefois, elle peut modifier le contenu de l'objet sur lequel il pointe. Pour la plupart des applications, ce procédé est suffisant et débouche sur le comportement désiré.

Si une méthode doit retourner une instance différente, utilisez la valeur de retour de la méthode pour obtenir ce résultat. Reportez-vous à la classe System.String pour connaître diverses méthodes qui fonctionnent sur les chaînes et retournent une nouvelle instance d'une chaîne. Lorsque ce modèle est utilisé, l'appelant doit décider si l'objet d'origine est ou non conservé.

Bien que les valeurs de retour soient banales et très utilisées, l'application correcte des paramètres out et ref requiert des compétences en matière de conception et de codage de niveau intermédiaire. Les architectes de bibliothèques qui réalisent un travail de conception destiné à une audience générale ne doivent pas s'attendre à ce que les utilisateurs maîtrisent l'exploitation des paramètres out ou ref.

Comment corriger les violations

Pour corriger une infraction à cette règle provoquée par un type valeur, faites en sorte que la méthode retourne l'objet comme sa valeur de retour. Si la méthode doit retourner plusieurs valeurs, refondez sa conception afin qu'elle retourne une unique instance d'un objet qui contient les valeurs.

Pour corriger une infraction à cette règle provoquée par un type référence, assurez-vous que le retour d'une nouvelle instance de la référence correspond bien au comportement désiré. Dans l'affirmative, pour y parvenir, la méthode doit utiliser sa valeur de retour.

Quand supprimer les avertissements

Il est possible de supprimer sans risque un avertissement de cette règle. Toutefois, cette conception peut engendre des problèmes d'utilisation.

Exemple

La bibliothèque suivante présente deux implémentations d'une classe qui génère des réponses aux commentaires d'un utilisateur. La première implémentation (BadRefAndOut) contraint l'utilisateur de la bibliothèque à gérer trois valeurs de retour. La seconde implémentation (RedesignedRefAndOut) simplifie l'interaction de l'utilisateur en retournant une instance d'une classe de conteneur (ReplyData) qui gère les données sous la forme d'une unité unique.

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

L'application suivante illustre l'interaction de l'utilisateur. L'appel à la bibliothèque dont la conception a été refondue (méthode UseTheSimplifiedClass) est plus simple, et les informations retournées par la méthode sont gérées facilement. La sortie des deux méthodes est identique.

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

La bibliothèque exemple suivante illustre l'utilisation des paramètres ref des types référence, ainsi qu'une manière plus efficace d'implémenter cette fonctionnalité.

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

L'application suivante appelle chaque méthode de la bibliothèque pour illustrer le comportement.

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

Cet exemple génère la sortie suivante :

  

Méthodes du modèle Try

Description

Les méthodes qui implémentent le modèle Try<quelque chose>, tel que Int32TryParse(), ne déclenchent pas cette violation. L'exemple suivant présente une structure (type valeur) qui implémente la méthode Int32TryParse().

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

Règles connexes

CA1045 : Ne pas passer de types par référence