CA1021: evitar parâmetros de saída
TypeName |
AvoidOutParameters |
CheckId |
CA1021 |
Categoria |
Microsoft.Design |
Alteração Significativa |
Quebra |
Causa
Um público ou um método protegido em um tipo utilitário têm um parâmetro de out .
Descrição da Regra
Passar tipos por referência (usando out ou ref) requer experiência com ponteiros, compreender como os tipos de valor e os tipos de referência são diferentes, e os métodos de manipulação com vários valores de retorno.Além disso, a diferença entre out e os parâmetros de ref não são compreendidos muito.
Quando um tipo de referência é passado por referência “,” o método pretende usar o parâmetro para retornar uma instância diferente do objeto.Passando um tipo de referência por referência também é conhecido como usar um ponteiro vezes, o ponteiro para um ponteiro, ou de nomes indiretos duplo.Usando a convenção padrão de chamada, que é passagem pelo valor “,” um parâmetro que usa um tipo de referência já recebe um ponteiro para o objeto.O ponteiro, não ao objeto que aponta, é passado pelo valor.A passagem pelo valor significa que o método não pode alterar o ponteiro para o que aponte para uma nova instância do tipo de referência.No entanto, pode modificar o conteúdo do objeto ao qual aponta.Para a maioria dos aplicativos isso é suficiente e gerencie o comportamento desejado.
Se um método deve retornar uma instância diferente, use o valor de retorno do método para fazer isso.Consulte a classe de String para uma variedade de métodos que operam em cadeias de caracteres e retornam uma nova instância de uma cadeia de caracteres.Quando esse modelo é usado, o chamador deve decidir se o objeto original é preservado.
Embora os valores de retorno são comuns e intensamente usado, o aplicativo correto de out e os parâmetros de ref exigem habilidades intermediários de design e de codificação.Os arquitetos de biblioteca que criam para um público geral não devem esperar que os usuários dominar trabalhar com out ou parâmetros de ref .
Como Corrigir Violações
Para corrigir uma violação dessa regra é causada por um tipo de valor, tem o retorno do método do objeto como seu valor de retorno.Se o método deve retornar vários valores, remodele-o para retornar uma única instância de um objeto que contém os valores.
Para corrigir uma violação dessa regra é causada por um tipo de referência, certifique-se de que o comportamento desejado é retornar uma nova instância de referência.Se for, o método deve usar o valor de retorno para fazer isso.
Quando Suprimir Alertas
É seguro suprimir um aviso desta regra.No entanto, esse design pode causar problemas de usabilidade.
Exemplo
A biblioteca seguir mostra duas implementações de uma classe que gerencia respostas aos comentários de um usuário.A primeira implementação (BadRefAndOut) força o usuário de biblioteca para gerenciar três valores de retorno.A segunda implementação (RedesignedRefAndOut) simplifica a experiência do usuário retornando uma instância de uma classe do contêiner (ReplyData) que gerencia os dados como uma única unidade.
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;
}
}
}
O aplicativo seguir ilustra a experiência do usuário.A chamada para a biblioteca alto (método deUseTheSimplifiedClass ) é mais simples, e as informações retornada pelo método é gerenciado facilmente.A saída dos dois métodos são idênticas.
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();
}
}
}
Biblioteca de exemplo a seguir ilustra como os parâmetros de ref para tipos de referência são usados e mostra uma maneira ideal de implementar essa funcionalidade.
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";
}
}
}
O seguinte aplicativo chama cada método na biblioteca para demonstrar o 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);
}
}
}
O exemplo produz a seguinte saída.
Métodos de padrão da tentativa
Descrição
Os métodos que implementam o padrão de Tentativa<Algo> , como Int32.TryParse, não lançam essa violação.O exemplo a seguir mostra uma estrutura (tipo de valor) que implementa o método de Int32.TryParse .
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;
}
}
}