Condividi tramite


CA1021: Evitare parametri out

TypeName

AvoidOutParameters

CheckId

CA1021

Categoria

Microsoft.Design

Breaking Change

Breaking

Causa

Un metodo pubblico o protetto in un tipo pubblico presenta un parametro out.

Descrizione della regola

Il passaggio di tipi per riferimento (mediante out o ref) richiede esperienza con i puntatori, conoscenza delle differenze tra tipi di valore e tipi di riferimento, nonché conoscenza dei metodi con più valori restituiti.La differenza tra parametri out e ref spesso non è compresa.

Quando un tipo di riferimento viene passato "per riferimento", il metodo utilizza il parametro per restituire un'istanza diversa dell'oggetto.Il passaggio di un tipo di riferimento per riferimento è anche noto come utilizzo di un doppio puntatore, di un puntatore a un puntatore o di un doppio riferimento indiretto.Utilizzando la convenzione di chiamata predefinita, ovvero il passaggio "per valore", un parametro che accetta un tipo di riferimento riceve già un puntatore all'oggetto.Il puntatore, non l'oggetto a cui punta, viene passato per valore.Passaggio per valore significa che il metodo non può modificare il puntatore affinché faccia riferimento a una nuova istanza del tipo di riferimento.Tuttavia, il puntatore può modificare il contenuto dell'oggetto a cui punta.Per la maggior parte delle applicazione questo è sufficiente e fornisce il comportamento desiderato.

Se un metodo deve restituire un'istanza diversa, utilizzare a tale scopo il valore restituito del metodo.Per apprendere diversi metodi che operano su stringhe e restituiscono una nuova istanza di una stringa, vedere la classe String.Quando si utilizza questo modello, il chiamante deve decidere se conservare l'oggetto originale.

Sebbene i valori restituiti siano comuni e ampiamente utilizzati, l'applicazione corretta dei parametri out e ref richiede competenze in progettazione intermedia e codifica.I progettisti di librerie che progettano per destinatari generici non possono prevedere che gli utenti utilizzino in modo professionale i parametri out o ref.

Come correggere le violazioni

Per correggere una violazione di questa regola causata da un tipo valore, fare in modo che il metodo restituisca l'oggetto come valore restituito.Se è necessario che il metodo restituisca più valori, riprogettarlo affinché restituisca una sola istanza di un oggetto contenente i valori.

Per correggere una violazione di questa regola causata da un tipo riferimento, assicurarsi che la restituzione di una nuova istanza del riferimento sia il comportamento desiderato.In questo caso, il metodo deve utilizzare a tale scopo il proprio valore restituito.

Esclusione di avvisi

L'esclusione di un avviso da questa regola è sicura.Tuttavia, questa progettazione potrebbe presentare problemi di funzionalità.

Esempio

Nella seguente libreria sono illustrate due implementazioni di una classe che genera risposte al feedback di un utente.La prima implementazione (BadRefAndOut) impone all'utente della libreria di gestire tre valori restituiti.La seconda implementazione (RedesignedRefAndOut) semplifica l'utilizzo da parte dell'utente restituendo un'istanza di una classe contenitore (ReplyData) che gestisce i dati come una singola unità.

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'applicazione riportata di seguito illustra l'attività dell'utente.La chiamata alla libreria riprogettata (metodo UseTheSimplifiedClass) è più semplice e le informazioni restituite dal metodo vengono gestite in modo più agevole.L'output dei due metodi è identico.

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 libreria di esempio riportata di seguito illustra l'utilizzo dei parametri ref per i tipi di riferimento e mostra un modo migliore per implementare questa funzionalità.

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'applicazione riportata di seguito chiama ogni metodo della libreria e ne illustra il comportamento.

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

Questo esempio produce l'output che segue.

  

Metodi con modello Try

Descrizione

I metodi che implementano il criterio Try<Something> come ad esempio Int32.TryParse, non generano questa violazione.Nell'esempio riportato di seguito viene illustrata una struttura (tipo valore) che implementa il metodo Int32.TryParse.

Codice

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

Regole correlate

CA1045: Non passare i tipi per riferimento