CA1045 : Ne pas passer de types par référence

Propriété Value
Identificateur de la règle CA1045
Titre Ne pas passer de types par référence
Catégorie Conception
Le correctif est cassant ou non cassant Rupture
Activé par défaut dans .NET 8 Non

Cause

Une méthode publique ou protégée dans un type public comporte un paramètre ref qui prend un type primitif, un type référence ou un type valeur qui n’est pas l’un 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 renvoyées. Par ailleurs, la différence entre les paramètres out et ref est généralement peu comprise.

Lorsqu’un type référence est passé « par référence », la méthode a l’intention d’utiliser le paramètre pour retourner une autre instance de l’objet. (Le passage d’un type référence par référence est également appelé utilisation d’un pointeur double, d’un pointeur vers un pointeur ou d’une double indirection.) À l’aide de la convention d’appel par défaut, qui consiste à passer « par valeur », un paramètre qui prend un type référence reçoit déjà un pointeur vers l’objet. Le pointeur, et non l’objet vers lequel il pointe, est passé par valeur. Le passage par valeur signifie que la méthode ne peut pas modifier le pointeur de manière à ce qu’il pointe vers une nouvelle instance du type référence, mais peut modifier le contenu de l’objet vers lequel il pointe. Pour la plupart des applications, cela suffit et génère le comportement souhaité.

Si une méthode doit retourner une autre instance, utilisez la valeur renvoyée de la méthode pour effectuer cette opération. Consultez la classe System.String pour obtenir une variété de méthodes qui fonctionnent sur des chaînes et retournent une nouvelle instance d’une chaîne. À l’aide de ce modèle, il appartient à l’appelant de décider si l’objet d’origine est conservé.

Bien que les valeurs renvoyées soient courantes et fortement utilisées, l’application correcte des paramètres out et ref nécessite des compétences de conception et de codage intermédiaires. 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’utilisation des paramètres out ou ref.

Notes

Lorsque vous travaillez avec des paramètres qui sont des structures volumineuses, les ressources supplémentaires requises pour copier ces structures peuvent entraîner un effet de performances lorsque vous passez par valeur. Dans ces cas, vous pouvez envisager d’utiliser les paramètres ref ou out.

Comment corriger les violations

Pour corriger une violation de cette règle causée par un type valeur, faites en sorte que la méthode retourne l’objet comme valeur de retour. Si la méthode doit retourner plusieurs valeurs, remaniez-la de manière à retourner une seule instance d’un objet qui contient les valeurs.

Pour corriger une violation de cette règle provoquée par un type référence, assurez-vous que le comportement souhaité est de retourner une nouvelle instance de la référence. Si c’est le cas, la méthode doit utiliser sa valeur renvoyée pour effectuer cette opération.

Quand supprimer les avertissements

Il est sûr de supprimer un avertissement de cette règle ; toutefois, cette conception peut entraîner des problèmes d’utilisation.

Supprimer un avertissement

Si vous voulez supprimer une seule violation, ajoutez des directives de préprocesseur à votre fichier source pour désactiver et réactiver la règle.

#pragma warning disable CA1045
// The code that's violating the rule is on this line.
#pragma warning restore CA1045

Pour désactiver la règle sur un fichier, un dossier ou un projet, définissez sa gravité sur none dans le fichier de configuration.

[*.{cs,vb}]
dotnet_diagnostic.CA1045.severity = none

Pour plus d’informations, consultez Comment supprimer les avertissements de l’analyse de code.

Configurer le code à analyser

Utilisez l’option suivante pour configurer les parties de votre codebase sur lesquelles exécuter cette règle.

Vous pouvez configurer cette option pour cette règle uniquement, pour toutes les règles auxquelles elle s’applique ou pour toutes les règles de cette catégorie (Conception) auxquelles elle s’applique. Pour plus d’informations, consultez Options de configuration des règles de qualité du code.

Inclure des surfaces d’API spécifiques

Vous pouvez configurer les parties de votre codebase sur lesquelles exécuter cette règle, en fonction de leur accessibilité. Par exemple, pour spécifier que la règle doit s’exécuter uniquement sur la surface d’API non publique, ajoutez la paire clé-valeur suivante à un fichier .editorconfig dans votre projet :

dotnet_code_quality.CAXXXX.api_surface = private, internal

Exemple 1

La bibliothèque suivante montre deux implémentations d’une classe qui génère des réponses aux commentaires de l’utilisateur. La première implémentation (BadRefAndOut) force l’utilisateur de la bibliothèque à gérer trois valeurs renvoyées. La deuxième implémentation (RedesignedRefAndOut) simplifie l’expérience utilisateur en retournant une instance d’une classe de conteneur (ReplyData) qui gère les données comme une seule unité.

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

Exemple 2

L’application suivante illustre l’expérience de l’utilisateur. L’appel à la bibliothèque repensée (méthode UseTheSimplifiedClass) est plus simple et les informations retournées par la méthode sont facilement gérées. La sortie des deux méthodes est identique.

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 Main1045()
    {
        UseTheComplicatedClass();

        // Print a blank line in output.
        Console.WriteLine("");

        UseTheSimplifiedClass();
    }
}

Exemple 3

L’exemple de bibliothèque suivant illustre la façon dont les paramètres ref des types de référence sont utilisés et montre une meilleure façon d’implémenter cette fonctionnalité.

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

Exemple 4

L’application suivante appelle chaque méthode de la bibliothèque pour illustrer le comportement.

public class Test
{
    public static void Main1045()
    {
        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 produit la sortie suivante :

Changing pointer - passed by value:
12345
12345
Changing pointer - passed by reference:
12345
12345 ABCDE
Passing by return value:
12345 ABCDE

CA1021 : Éviter les paramètres out