CA1021:避免使用 out 参数

类型名

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<Something> 模式的方法(例如 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:不要通过引用来传递类型