CA1045 : Ne pas passer de types par référence
TypeName |
DoNotPassTypesByReference |
CheckId |
CA1045 |
Catégorie |
Microsoft.CSharp |
Modification avec rupture |
Oui |
Cause
Une méthode publique ou protégée dans un type public est dotée d'un paramètre ref qui accepte un type primitif, un type référence ou un type valeur qui ne fait pas partie des types intégrés.
Description de la règle
Passer des types par référence (en utilisant out ou ref) nécessite une certaine expérience des pointeurs, de comprendre la différence entre les types valeur et les types référence, ainsi que la gestion de méthodes impliquant plusieurs valeurs de retour. Par ailleurs, la différence entre les paramètres out et refest généralement peu comprise.
Lorsqu'un type référence est passé "par référence", la méthode projette d'utiliser le paramètre pour retourner une instance différente de l'objet. (Passer un type référence par référence est également connu comme l'utilisation d'un pointeur double, d'un pointeur vers un pointeur, ou d'une indirection double.) À l'aide de la convention d'appel par défaut, qui revient à passer "par valeur", un paramètre qui accepte un type référence reçoit déjà un pointeur vers l'objet. Le pointeur, et non l'objet sur lequel il pointe, est passé par valeur. Passer par valeur signifie que la méthode ne peut pas modifier le pointeur afin que celui-ci pointe sur une nouvelle instance du type référence, mais qu'elle peut modifier le contenu de l'objet sur lequel il pointe. Pour la plupart des applications, ce procédé est suffisant et débouche sur le comportement que vous souhaitez.
Si une méthode doit retourner une instance différente, utilisez la valeur de retour de la méthode pour obtenir ce résultat. Reportez-vous à la classe System.String pour connaître diverses méthodes qui fonctionnent sur les chaînes et retournent une nouvelle instance d'une chaîne. En utilisant ce modèle, l'appelant est libre de décider si l'objet d'origine est conservé.
Bien que les valeurs de retour soient banales et très utilisées, l'application correcte des paramètres out et ref requiert des compétences en matière de conception et de codage de niveau intermédiaire. Les architectes de bibliothèques qui réalisent un travail de conception destiné à une audience générale ne doivent pas s'attendre à ce que les utilisateurs maîtrisent l'exploitation des paramètres out ou ref.
Notes
Lors de l'utilisation des paramètres qui sont des structures de grande taille, les ressources supplémentaires requises pour copier ces structures peuvent avoir des conséquences sur les performances lors du passage par valeur. Dans ces cas, vous pouvez envisager d'utiliser des paramètres ref ou out.
Comment corriger les violations
Pour corriger une infraction à cette règle provoquée par un type valeur, faites en sorte que la méthode retourne l'objet comme sa valeur de retour. Si la méthode doit retourner plusieurs valeurs, refondez sa conception afin qu'elle retourne une unique instance d'un objet qui contient les valeurs.
Pour corriger une infraction à cette règle provoquée par un type référence, assurez-vous que le retour d'une nouvelle instance de la référence correspond bien au comportement que vous souhaitez. Dans l'affirmative, pour y parvenir, la méthode doit utiliser sa valeur de retour.
Quand supprimer les avertissements
Il est possible de supprimer sans risque un avertissement de cette règle ; toutefois, ce design peut engendrer des problèmes d'utilisation.
Exemple
La bibliothèque suivante présente deux implémentations d'une classe qui génère des réponses aux commentaires de l'utilisateur. La première implémentation (BadRefAndOut) contraint l'utilisateur de la bibliothèque à gérer trois valeurs de retour. La seconde implémentation (RedesignedRefAndOut) simplifie l'interaction de l'utilisateur en retournant une instance d'une classe de conteneur (ReplyData) qui gère les données sous la forme d'une unité unique.
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'application suivante illustre l'interaction de l'utilisateur. L'appel à la bibliothèque dont la conception a été refondue (méthode UseTheSimplifiedClass) est plus simple, et les informations retournées par la méthode sont gérées facilement. La sortie des deux méthodes est identique.
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 bibliothèque exemple suivante illustre l'utilisation des paramètres ref des types référence, ainsi qu'une manière plus efficace d'implémenter cette fonctionnalité.
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'application suivante appelle chaque méthode de la bibliothèque pour illustrer le comportement.
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);
}
}
}
Cet exemple génère la sortie suivante :