Поделиться через


CA1021: не используйте параметры out

TypeName

AvoidOutParameters

CheckId

CA1021

Категория

Microsoft.Design

Критическое изменение

Критическое изменение

Причина

Открытый или защищенный метод открытого типа имеет выходной (out) параметр.

Описание правила

Чтобы реализовать передачу типов по ссылке (с помощью ключевого слова out или ref), от разработчика требуется опыт работы с указателями, понимание отличия между типами значения и ссылочными типами и умение управлять методами с несколькими возвращаемыми значениями. Кроме того, далеко не каждый понимает разницу между параметрами out и ref.

Если ссылочный тип передается "по ссылке", метод предполагает использовать параметр для возвращения другого экземпляра объекта. Передачу ссылочного типа по ссылке также называют использованием двойного указателя, указателя на указатель или двойного косвенного обращения. В соответствии с соглашением о вызовах, в котором по умолчанию используется передача "по значению", параметр, принимающий ссылочный тип, уже получает указатель на объект. Указатель (а не объект, на который он указывает) передается по значению. Передача по значению означает, что метод не может изменить указатель таким образом, чтобы он указывал на новый экземпляр ссылочного типа. Однако метод может изменить содержимое объекта, на который указывает указатель. Для большинства приложений этого достаточно и обеспечивает желаемое поведение.

Если метод должен возвращать другой экземпляр, следует использовать возвращаемое значение метода. Для получения сведений о методах, которые обрабатывают строки и возвращают новый экземпляр строки, см. в описании класса System.String. При использовании данной модели вызывающему объекту необходимо решить, следует ли сохранять исходный объект.

Конечно, возвращаемые значения всем знакомы и повсеместно используются, однако для правильного применения параметров out и ref требуется средний уровень навыков проектирования и кодирования. Архитекторам, разрабатывающим библиотеки для широкого использования, не следует ожидать, что пользователи являются профессионалами в использовании параметров out и ref.

Устранение нарушений

Для устранения нарушения данного правила, вызванного типом значения, необходимо, чтобы метод возвращал объект в качестве своего возвращаемого значения. Если метод должен возвращать несколько значений, измените его таким образом, чтобы он возвращал единственный экземпляр объекта, содержащего значения.

Для устранения нарушения данного правила, вызванного ссылочным типом, следует убедиться, что действительно требуется возвращать новый экземпляр ссылки. Если такое поведения является необходимым, то для этого следует использовать возвращаемое значение метода.

Отключение предупреждений

Отключение предупреждений о нарушении этого правила является безопасным. Однако подобная структура кода может приводить к проблемам удобства использования.

Пример

В следующей библиотеке показаны две реализации класса, который создает отклики на ввод пользователя. Первая реализация (BadRefAndOut) вынуждает пользователя библиотеки управлять тремя возвращаемыми значениями. Во второй реализации (RedesignedRefAndOut) работа пользователя упрощается за счет возвращения класса-контейнера (ReplyData), который управляет данными как единым блоком.

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

В следующем приложении демонстрируется работа пользователя. Вызов переработанной библиотеки (метода UseTheSimplifiedClass) более понятен, и сведениями, возвращаемыми данным методом, легче управлять. Выходные данные, возвращаемые обоими методами, идентичны.

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

В следующем примере библиотеки демонстрируется использование параметров ref для ссылочных типов. Здесь также показан оптимальный способ реализации данной функциональной возможности.

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

В следующем приложении для демонстрации поведения библиотеки вызывается каждый ее метод.

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

После выполнения примера получается следующий результат.

  

Методы, использующие шаблон Try

Описание

Использование методов, которые реализуют шаблон Try<действие>, например Int32TryParse(), не приводит к нарушению данного правила. В следующем примере показана структура (тип значения), которая реализует метод Int32TryParse().

Код

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

Связанные правила

CA1045: не передавайте типы по ссылке