CA1021: Evite os parâmetros de saída
TypeName |
AvoidOutParameters |
CheckId |
CA1021 |
<strong>Categoria</strong> |
Microsoft.design |
Alteração significativa |
Quebrando |
Causa
Um método público ou protegido em um tipo público tem um out parâmetro.
Descrição da regra
Tipos de passagem por referência (usando out ou ref) requer experiência com ponteiros, entender a diferença entre os tipos de valor e tipos de referência e tratamento de métodos com vários valores de retorno. Além disso, a diferença entre out e ref parâmetros não é amplamente sabido.
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. Um tipo de referência de passagem por referência também é conhecido como usando um ponteiro duplo, o ponteiro para um ponteiro ou um indireção dupla. Usando o padrão de convenção de chamada, passar "por valor" um parâmetro que leva um tipo de referência já recebe um ponteiro para o objeto. O ponteiro, e não o objeto para o qual ele aponta, é passado por valor. Passe por meio de valor que o método não é possível alterar o ponteiro para que ele aponte para uma nova instância do tipo de referência. No entanto, ele pode alterar o conteúdo do objeto ao qual ele aponta. Para a maioria dos aplicativos, isso é suficiente e produz 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 o System.String classe para uma variedade de métodos que operam em seqüências de caracteres e retornar uma nova instância de uma seqüência de caracteres. Quando esse modelo é usado, o chamador deve decidir se o objeto original é preservado.
Embora os valores de retorno são comuns e muito usado, a aplicação correta das out e ref parâmetros requer design intermediário e habilidades de codificação. Os arquitetos de biblioteca que o design para o público em geral não deve esperar que os usuários mestre trabalhando com out ou ref parâmetros.
Como corrigir violações
Para corrigir uma violação desta regra que é causado por um tipo de valor, ter o método para retornar o objeto como valor de retorno. Se o método deve retornar vários valores, recriá-lo para retornar uma única instância de um objeto que contém os valores.
Para corrigir uma violação desta regra que é causado por um tipo de referência, certifique-se de que o comportamento desejado é para retornar uma nova instância da referência. Se for, o método deve usar o valor de retorno para fazer isso.
Quando suprimir avisos
É seguro eliminar um aviso esta regra. No entanto, esse design pode causar problemas de usabilidade.
Exemplo
A biblioteca a seguir mostra duas implementações de uma classe que gera respostas para os comentários de um usuário. A primeira implementação (BadRefAndOut) força o usuário da biblioteca para gerenciar três valores de retorno. A implementação de segunda (RedesignedRefAndOut) simplifica a experiência do usuário, retornando uma instância de uma classe de 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 a seguir ilustra a experiência do usuário. A chamada para a biblioteca reprojetada (UseTheSimplifiedClass método) é mais simples, e as informações retornadas pelo método é facilmente gerenciadas. A saída dos dois métodos é idêntica.
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();
}
}
}
A biblioteca de exemplo a seguir ilustra como ref parâmetros de tipos de referência são usados e mostra uma melhor maneira 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.
Tente os métodos padrão
Descrição
Métodos que implementam o tente <Something> padrão, como Int32TryParse(), não aumente essa violação. O exemplo a seguir mostra uma estrutura (tipo de valor) que implementa o Int32TryParse() método.
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;
}
}
}