Quel est le meilleur, valeur de retour ou paramètre de sortie?

147

Si nous voulons obtenir une valeur d'une méthode, nous pouvons utiliser l'une ou l'autre des valeurs de retour, comme ceci:

public int GetValue(); 

ou:

public void GetValue(out int x);

Je ne comprends pas vraiment les différences entre eux, donc je ne sais pas ce qui est le mieux. Pouvez-vous m'expliquer cela?

Je vous remercie.

Quan Mai
la source
3
Je souhaite que C # ait plusieurs valeurs de retour comme Python, par exemple.
Trap
12
@Trap Vous pouvez renvoyer a Tuplesi vous le souhaitez, mais le consensus général est que si vous avez besoin de renvoyer plus d'une chose, les choses sont généralement liées d'une manière ou d'une autre et cette relation est généralement mieux exprimée en tant que classe.
Pharap
2
@Pharap Tuples en C # dans sa forme actuelle est juste moche, mais ce n'est que mon avis. D'un autre côté, le "consensus général" ne veut rien dire du point de vue de la convivialité et de la productivité. Vous ne créeriez pas de classe pour renvoyer quelques valeurs pour la même raison que vous ne créeriez pas de classe pour renvoyer quelques valeurs en tant que paramètre ref / out.
Trap
@Trap return Tuple.Create(x, y, z);N'est-ce pas moche. De plus, il est tard dans la journée de les faire introduire au niveau de la langue. La raison pour laquelle je ne créerais pas de classe pour renvoyer les valeurs d'un paramètre ref / out est que les paramètres ref / out n'ont vraiment de sens que pour les grandes structures mutables (comme les matrices) ou les valeurs facultatives, et ce dernier est discutable.
Pharap
L'équipe @Pharap C # cherche activement à introduire des tuples au niveau du langage. Bien qu'il soit le bienvenu, la pléthore d'options de .NET est désormais écrasante - type anonyme, .NET Tuple<>et C # tuples !. Je souhaitais juste que C # autorise le retour de types anonymes à partir de méthodes avec un type d'inférence de compilateur (comme autodans Dlang).
nawfal

Réponses:

153

Les valeurs de retour sont presque toujours le bon choix lorsque la méthode n'a rien d'autre à renvoyer. (En fait, je ne peux pas penser à tous les cas où je serais jamais veux une méthode vide avec un outparamètre, si j'avais le choix. C # 7 deDeconstruct méthodes de déconstruction du langage soutenu agit comme une exception très, très rare à cette règle .)

Mis à part toute autre chose, cela empêche l'appelant d'avoir à déclarer la variable séparément:

int foo;
GetValue(out foo);

contre

int foo = GetValue();

Les valeurs de sortie empêchent également le chaînage de méthodes comme ceci:

Console.WriteLine(GetValue().ToString("g"));

(En effet, c'est aussi l'un des problèmes avec les setters de propriété, et c'est pourquoi le modèle de générateur utilise des méthodes qui retournent le générateur, par exemple myStringBuilder.Append(xxx).Append(yyy) .)

De plus, les paramètres de sortie sont légèrement plus difficiles à utiliser avec la réflexion et rendent généralement les tests plus difficiles. (Plus d'efforts sont généralement déployés pour faciliter la simulation des valeurs de retour que des paramètres out). Fondamentalement, je ne peux penser à rien de ce qu'ils font facilitent ...

Renvoie les valeurs FTW.

EDIT: En termes de ce qui se passe ...

Fondamentalement, lorsque vous passez un argument pour un paramètre "out", vous devez passer une variable. (Les éléments du tableau sont également classés comme des variables.) La méthode que vous appelez n'a pas de "nouvelle" variable sur sa pile pour le paramètre - elle utilise votre variable pour le stockage. Toute modification de la variable est immédiatement visible. Voici un exemple montrant la différence:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

Résultats:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

La différence se situe à l'étape «post» - c'est-à-dire après que la variable locale ou le paramètre a été modifié. Dans le test ReturnValue, cela ne fait aucune différence pour la valuevariable statique . Dans le test OutParameter, la valuevariable est modifiée par la lignetmp = 10;

Jon Skeet
la source
2
Vous m'avez fait croire que la valeur de retour est bien meilleure :). Mais je me demande encore ce qui se passe "en profondeur". Je veux dire, la valeur de retour et le paramètre out, sont-ils différents dans la manière dont ils sont créés, affectés et retournés?
Quan Mai
1
TryParse est le meilleur exemple d'utilisation d'un paramètre out est approprié et propre. Bien que je l'ai utilisé dans des cas particuliers comme if (WorkSucceeded (out List <string> errors) qui est fondamentalement le même modèle que TryParse
Chad Grant
2
Une mauvaise réponse inutile. Comme expliqué dans les commentaires sur cette question , les paramètres out présentent plusieurs avantages utiles. La préférence entre aller et retour devrait dépendre de la situation.
aaronsnoswell
2
@aaronsnoswell: Quels commentaires précis? Gardez à l'esprit que l'exemple de Dictionary.TryGetValuen'est pas applicable ici, car ce n'est pas une méthode vide. Pouvez-vous expliquer pourquoi vous voudriez un outparamètre au lieu d'une valeur de retour? (Même pour TryGetValue, je préfère une valeur de retour qui contient toutes les informations de sortie, personnellement. Voir NodaTime ParseResult<T>pour un exemple de la façon dont je l'aurais conçu.)
Jon Skeet
2
@Jim: Je suppose que nous devrons accepter de ne pas être d'accord. Alors que je vois votre point au sujet de coups de marteau sur la tête des gens avec cela, il fait aussi moins convivial pour ceux qui ne savent ce qu'ils font. La bonne chose à propos d'une exception plutôt qu'une valeur de retour est que vous ne pouvez pas facilement l' ignorer et continuer comme si de rien n'était ... alors qu'avec une valeur de retour et un outparamètre, vous ne pouvez tout simplement rien faire avec la valeur.
Jon Skeet
26

Ce qui est mieux, dépend de votre situation particulière. L'une des raisons outexiste est de faciliter le retour de plusieurs valeurs à partir d'un appel de méthode:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

Donc l'un n'est pas par définition meilleur que l'autre. Mais généralement, vous voudrez utiliser un simple retour, sauf si vous avez la situation ci-dessus par exemple.

EDIT: Ceci est un exemple montrant l'une des raisons pour lesquelles le mot-clé existe. Ce qui précède ne doit en aucun cas être considéré comme une bonne pratique.

pyrocumulus
la source
2
Je ne suis pas d'accord avec cela, pourquoi ne retourneriez-vous pas une structure de données avec 4 pouces? C'est vraiment déroutant.
Chad Grant
1
De toute évidence, il existe plus (et de meilleures) façons de renvoyer plusieurs valeurs, je donne simplement à l'OP une raison pour laquelle out existe en premier lieu.
pyrocumulus
2
Je suis d'accord avec @Cloud. Ce n'est pas parce que ce n'est pas le meilleur moyen qu'il ne devrait pas exister.
Cerebrus
Eh bien, le code ne se compilera même pas pour un seul ... et il devrait au moins mentionner que c'est considéré comme une mauvaise pratique / pas préféré.
Chad Grant
1
Il ne se compilera pas? Et pourquoi est-ce que? Cet échantillon pourrait compiler tout simplement parfait. Et s'il vous plaît, je donne un exemple de pourquoi une syntaxe / mot-clé particulier existe, pas une leçon sur les meilleures pratiques.
pyrocumulus
23

Vous devriez généralement préférer une valeur de retour à un paramètre de sortie. Les paramètres de sortie sont un mal nécessaire si vous vous retrouvez à écrire du code qui doit faire 2 choses. Un bon exemple de ceci est le modèle Try (tel que Int32.TryParse).

Considérons ce que l'appelant de vos deux méthodes devrait faire. Pour le premier exemple, je peux écrire ceci ...

int foo = GetValue();

Notez que je peux déclarer une variable et l'affecter via votre méthode en une seule ligne. Pour le 2ème exemple, cela ressemble à ceci ...

int foo;
GetValue(out foo);

Je suis maintenant obligé de déclarer ma variable à l'avance et d'écrire mon code sur deux lignes.

mettre à jour

Les directives de conception du .NET Framework constituent un bon endroit pour poser ces types de questions. Si vous avez la version du livre, vous pouvez voir les annotations d'Anders Hejlsberg et d'autres sur ce sujet (pages 184-185) mais la version en ligne est ici ...

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

Si vous avez besoin de renvoyer deux choses à partir d'une API, les encapsuler dans une structure / classe serait mieux qu'un paramètre out.

Martin Peck
la source
Excellente réponse, en particulier la référence à TryParse, une fonction (commune) qui oblige les développeurs à utiliser les variables de sortie (peu communes).
Cerebrus
12

Il y a une raison d'utiliser un outparamètre qui n'a pas déjà été mentionné: la méthode appelante est obligée de le recevoir. Si votre méthode produit une valeur que l'appelant ne doit pas ignorer, en faire un outoblige l'appelant à l'accepter spécifiquement:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

Bien sûr, l'appelant peut toujours ignorer la valeur d'un outparamètre, mais vous avez attiré son attention dessus.

C'est un besoin rare; le plus souvent, vous devriez utiliser une exception pour un problème réel ou renvoyer un objet avec des informations d'état pour un "FYI", mais il peut y avoir des circonstances où cela est important.


la source
8

C'est la préférence principalement

Je préfère les retours et si vous avez plusieurs retours, vous pouvez les envelopper dans un résultat DTO

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}
Scott Cowan
la source
5

Vous ne pouvez avoir qu'une seule valeur de retour alors que vous pouvez avoir plusieurs paramètres de sortie.

Il vous suffit de prendre en compte les paramètres dans ces cas.

Cependant, si vous devez renvoyer plus d'un paramètre à partir de votre méthode, vous voudrez probablement regarder ce que vous retournez d'une approche OO et déterminer s'il vaut mieux retourner un objet ou une structure avec ces paramètres. Par conséquent, vous êtes de nouveau à une valeur de retour.

Jour de Robin
la source
5

Vous devez presque toujours utiliser une valeur de retour. ' out' Les paramètres créent un peu de friction avec beaucoup d'API, de compositionnalité, etc.

L'exception la plus notable qui me vient à l'esprit est lorsque vous souhaitez renvoyer plusieurs valeurs (.Net Framework n'a pas de tuples jusqu'à 4.0), comme avec le TryParsemodèle.

Brian
la source
Je ne sais pas si c'est une bonne pratique, mais Arraylist peut également être utilisé pour renvoyer plusieurs valeurs.
BA
@BA non ce n'est pas une bonne pratique, les autres utilisateurs ne sauraient pas ce que contient cette Arraylist, ni à quelle position ils se trouvent. Par exemple, je veux renvoyer la quantité d'octets lus et également la valeur. out ou tuples qui sont nommés serait bien mieux. ArrayList, ou toute autre liste ne serait utile que si vous souhaitez renvoyer une collaction d'objets, par exemple une liste de personnes.
sLw
2

Je préférerais ce qui suit au lieu de l'un ou l'autre de ceux de cet exemple simple.

public int Value
{
    get;
    private set;
}

Mais, ils sont tous très similaires. Habituellement, on n'utiliserait «out» que s'il avait besoin de renvoyer plusieurs valeurs de la méthode. Si vous voulez envoyer une valeur dans et hors de la méthode, vous choisirez «ref». Ma méthode est la meilleure, si vous ne renvoyez qu'une valeur, mais si vous voulez passer un paramètre et récupérer une valeur, vous choisirez probablement votre premier choix.

Kenny
la source
2

Je pense que l'un des rares scénarios où cela serait utile serait lorsque vous travaillez avec de la mémoire non managée, et vous voulez faire comprendre que la valeur "retournée" doit être supprimée manuellement, plutôt que de s'attendre à ce qu'elle soit supprimée seule .

xian
la source
2

En outre, les valeurs de retour sont compatibles avec les paradigmes de conception asynchrones.

Vous ne pouvez pas désigner une fonction "async" si elle utilise des paramètres ref ou out.

En résumé, les valeurs de retour permettent le chaînage de méthodes, une syntaxe plus claire (en éliminant la nécessité pour l'appelant de déclarer des variables supplémentaires) et permettent des conceptions asynchrones sans nécessiter de modification substantielle dans le futur.

firetiger77
la source
Excellent point sur la désignation "asynchrone". Je pensais que quelqu'un d'autre aurait mentionné cela. Le chaînage - ainsi que l'utilisation de la valeur de retour (en fait la fonction elle-même) comme expression est un autre avantage clé. C'est vraiment la différence entre considérer uniquement comment récupérer des éléments d'un processus (le "bucket" - discussion Tuple / classe / struct) et traiter le processus lui-même comme une expression qui peut être substituée à une seule valeur (l'utilité de la fonction elle-même, car elle ne renvoie qu'une seule valeur).
user1172173
1

Les deux ont un but différent et ne sont pas traités de la même manière par le compilateur. Si votre méthode doit renvoyer une valeur, vous devez utiliser return. Out est utilisé lorsque votre méthode doit renvoyer plusieurs valeurs.

Si vous utilisez return, les données sont d'abord écrites dans la pile de méthodes, puis dans la méthode appelante. En cas de out, il est directement écrit dans la pile des méthodes appelantes. Je ne sais pas s'il y a d'autres différences.

danois
la source
La pile de méthodes? Je ne suis pas un expert en c #, mais x86 ne prend en charge qu'une seule pile par thread. Le "frame" de la méthode est désalloué lors du retour, et si un changement de contexte se produit alors la pile désallouée peut être écrasée. En c, toutes les valeurs de retour vont dans le registre eax. Si vous souhaitez renvoyer des objets / structures, ils doivent être alloués au tas et le pointeur sera placé dans eax.
Stefan Lundström
1

Comme d'autres l'ont dit: valeur de retour, pas de param.

Puis-je vous recommander le livre "Framework Design Guidelines" (2e éd)? Les pages 184-185 couvrent les raisons d'éviter les paramètres. L'ensemble du livre vous guidera dans la bonne direction sur toutes sortes de problèmes de codage .NET.

L'utilisation de l'outil d'analyse statique, FxCop, est alliée aux directives de conception de cadre. Vous le trouverez sur les sites de Microsoft en téléchargement gratuit. Exécutez ceci sur votre code compilé et voyez ce qu'il dit. S'il se plaint de centaines et de centaines de choses ... pas de panique! Regardez calmement et attentivement ce qu'il dit sur chaque cas. Ne vous précipitez pas pour réparer les choses dès que possible. Apprenez de ce qu'il vous dit. Vous serez mis sur la voie de la maîtrise.

Andrew Webb
la source
1

L'utilisation du mot-clé out avec un type de retour booléen peut parfois réduire le gonflement du code et augmenter la lisibilité. (Principalement lorsque les informations supplémentaires dans le paramètre out sont souvent ignorées.) Par exemple:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

contre:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}
Paul Smith
la source
1

Il n'y a pas de réelle différence. Les paramètres de sortie sont en C # pour permettre à la méthode de renvoyer plus d'une valeur, c'est tout.

Cependant, il existe de légères différences, mais aucune d'entre elles n'est vraiment importante:

L'utilisation du paramètre out vous obligera à utiliser deux lignes comme:

int n;
GetValue(n);

tout en utilisant la valeur de retour vous permettra de le faire en une seule ligne:

int n = GetValue();

Une autre différence (à corriger uniquement pour les types valeur et uniquement si C # n'inline pas la fonction) est que l'utilisation de la valeur de retour fera nécessairement une copie de la valeur lorsque la fonction retourne, tandis que l'utilisation du paramètre OUT ne le fera pas nécessairement.

user88637
la source
0

out est plus utile lorsque vous essayez de renvoyer un objet que vous déclarez dans la méthode.

Exemple

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}
Taera Kwon
la source
0

valeur de retour est la valeur normale renvoyée par votre méthode.

Où comme paramètre out , well out et ref sont 2 mots clés de C # ils permettent de passer des variables comme référence .

La grande différence entre ref et out est que ref doit être initialisé avant et out pas

bizimunda
la source
-2

Je suppose que je ne vais pas me renseigner sur cette question, mais je suis très programmeur expérimenté, et j'espère que certains des lecteurs les plus ouverts d'esprit y prêteront attention.

Je pense que cela convient mieux aux langages de programmation orientés objet que leurs procédures de retour de valeur (VRP) soient déterministes et pures.

«VRP» est le nom académique moderne d'une fonction appelée dans le cadre d'une expression, et a une valeur de retour qui remplace théoriquement l'appel lors de l'évaluation de l'expression. Par exemple, dans une instruction telle que x = 1 + f(y)la fonction fsert de VRP.

«Déterministe» signifie que le résultat de la fonction dépend uniquement des valeurs de ses paramètres. Si vous l'appelez à nouveau avec les mêmes valeurs de paramètres, vous êtes certain d'obtenir le même résultat.

«Pure» signifie pas d'effets secondaires: l'appel de la fonction ne fait rien d'autre que calculer le résultat. Cela peut être interprété comme ne signifiant aucun effet secondaire important , en pratique, donc si le VRP sort un message de débogage à chaque fois qu'il est appelé, par exemple, cela peut probablement être ignoré.

Ainsi, si, en C #, votre fonction n'est pas déterministe et pure, je dis que vous devriez en faire une voidfonction (en d'autres termes, pas un VRP), et toute valeur qu'elle doit retourner doit être retournée dans un outou un refparamètre.

Par exemple, si vous avez une fonction pour supprimer certaines lignes d'une table de base de données et que vous voulez qu'elle renvoie le nombre de lignes qu'elle a supprimées, vous devez la déclarer comme ceci:

public void DeleteBasketItems(BasketItemCategory category, out int count);

Si vous souhaitez parfois appeler cette fonction sans obtenir le count , vous pouvez toujours déclarer une surcharge.

Vous voudrez peut-être savoir pourquoi ce style convient mieux à la programmation orientée objet. En gros, il s'inscrit dans un style de programmation qui pourrait être (un peu imprécisément) appelé «programmation procédurale», et c'est un style de programmation procédurale qui convient mieux à la programmation orientée objet.

Pourquoi? Le modèle classique des objets est qu'ils ont des propriétés (aka attributs), et vous interrogez et manipulez l'objet (principalement) en lisant et en mettant à jour ces propriétés. Un style de programmation procédurale a tendance à faciliter cette tâche, car vous pouvez exécuter du code arbitraire entre les opérations qui obtiennent et définissent des propriétés.

L'inconvénient de la programmation procédurale est que, parce que vous pouvez exécuter du code arbitraire partout, vous pouvez obtenir des interactions très obtuses et vulnérables aux bogues via des variables globales et des effets secondaires.

Donc, tout simplement, il est bon de signaler à quelqu'un qui lit votre code qu'une fonction pourrait avoir des effets secondaires en la rendant sans valeur.

débatteur
la source
> Si vous voulez parfois appeler cette fonction mais pas obtenir le décompte, vous pouvez toujours déclarer une surcharge. Dans C # version 7 (je pense) et plus tard, vous pouvez utiliser le _symbole de suppression pour ignorer un paramètre out, par exemple: DeleteBasketItems (category, out _);
débatteur le