Evitar parámetros out

Actualización: noviembre 2007

Nombre de tipo

AvoidOutParameters

Identificador de comprobación

CA1021

Categoría

Microsoft.Design

Cambio problemático

Motivo

Un método público o protegido en un tipo público tiene un parámetro out.

Descripción de la regla

Para pasar tipos por referencia (utilizando out o ref) es necesario tener experiencia con punteros, saber la diferencia entre los tipos de referencia y los tipos de valor, y controlar métodos con múltiples valores devueltos. Además, no se suele saber qué diferencia hay entre los parámetros out y ref.

Cuando se pasa un tipo de referencia "por referencia", el método intenta utilizar el parámetro para devolver una instancia diferente del objeto. Pasar por referencia un tipo de referencia también se conoce como utilizar un puntero doble, un puntero a un puntero, o el direccionamiento indirecto doble. Al utilizar la convención de llamada predeterminada, que es pasa "por valor", un parámetro que toma un tipo de referencia recibe un puntero para el objeto. Se pasa el puntero por valor, no el objeto al que señala. Pasar por valor quiere decir que el método no puede modificar el puntero para que señale a una instancia nueva del tipo de referencia. Sin embargo, puede cambiar el contenido del objeto al que señala. En la mayoría de las aplicaciones, esto es suficiente y provoca el comportamiento deseado.

Si un método debe devolver una instancia diferente, utilice el valor devuelto del método para lograrlo. Vea la clase System.String para obtener múltiples métodos que funcionan en cadenas y devuelven una nueva instancia de una cadena. Cuando se usa este modelo, el llamador debe decidir si mantiene el objeto original.

Aunque los valores devueltos son comunes y muy utilizados, la aplicación correcta de los parámetros out y ref requiere un diseño intermedio y conocimiento del código. Los arquitectos de bibliotecas cuyos diseños están destinados a los usuarios en general no deben esperar que los usuarios dominen el uso de los parámetros out o ref.

Cómo corregir infracciones

Para corregir una infracción de esta regla producida por un tipo de valor, obligue al método a devolver el objeto como su valor devuelto. Si el método debe devolver varios valores, vuelva a diseñarlo para devolver una instancia única de un objeto que contiene los valores.

Para corregir esta infracción de esta regla producida por un tipo de referencia, asegúrese de que el comportamiento deseado es que devuelva una nueva instancia de la referencia. Si es así, el método debe utilizar su valor devuelto para ello.

Cuándo suprimir advertencias

Es seguro suprimir una advertencia de esta regla. Sin embargo, este diseño podría provocar problemas en la utilidad.

Ejemplo

La siguiente biblioteca muestra dos implementaciones de una clase que genera las respuestas a las indicaciones del usuario. La primera implementación (BadRefAndOut) fuerza al usuario de la biblioteca a administrar tres valores devueltos. La segunda implementación (RedesignedRefAndOut) reduce la experiencia del usuario devolviendo una instancia de la clase contenedora (ReplyData) que administra los datos como una sola unidad.

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

La aplicación siguiente muestra la experiencia del usuario. La llamada a la biblioteca rediseñada (método UseTheSimplifiedClass ) es más sencilla y la información devuelta por el método se administra con facilidad. El resultado de los dos métodos es idéntico.

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

El siguiente ejemplo de biblioteca muestra cómo se utilizan los parámetros ref para los tipos de referencia, y muestra una manera mejor de implementar esta funcionalidad.

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

La aplicación siguiente llama a cada método de la biblioteca para mostrar el comportamiento.

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

Este ejemplo produce el siguiente resultado.

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

Métodos del modelo Try

Descripción

Los métodos que implementan el modelo Try<Something>, como Int32TryParse(), no desencadenan esta infracción. En el ejemplo siguiente se muestra una estructura (tipo de valor) que implementa el método Int32TryParse().

Código

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

Reglas relacionadas

No pasar tipos por referencia