97 votes

Retourner deux valeurs, Tuple vs 'out' vs 'struct'.

Considérons une fonction qui renvoie deux valeurs. On peut l'écrire :

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)

Laquelle est la meilleure pratique et pourquoi ?

0 votes

String n'est pas un type de valeur. Je pense que vous vouliez dire "considérer une fonction qui renvoie deux valeurs".

0 votes

@Eric : Vous avez raison. Je voulais dire des types immuables.

0 votes

Et quel est le problème avec une classe ?

102voto

Eric Lippert Points 300275

Ils ont tous leurs avantages et leurs inconvénients.

Les paramètres externes sont rapides et bon marché, mais ils nécessitent de passer une variable et reposent sur la mutation. Il est presque impossible d'utiliser correctement un paramètre out avec LINQ.

Les tuples créent une pression de collecte 1 et ne sont pas auto-documentées. "Item1" n'est pas très descriptif.

Les structures personnalisées peuvent être lentes à copier si elles sont grandes, mais elles s'auto-documentent et sont efficaces si elles sont petites. Cependant, il est également pénible de définir un grand nombre de structures personnalisées pour des utilisations triviales.

Je pencherais pour la solution de la structure personnalisée, toutes choses égales par ailleurs. Mais ce qui est encore mieux, c'est de créer une fonction qui ne renvoie qu'une seule valeur . Pourquoi retournez-vous deux valeurs en premier lieu ?

Notez que les tuples dans C# 7, qui a été livré six ans après la rédaction de cette réponse, sont des types de valeur et donc moins susceptibles de créer une pression de collection.


1 Chaque fois que vous allouez un petit objet du tas, cela exerce une "pression" sur le collecteur d'ordures. Plus la pression est forte, plus les collectes sont fréquentes. Dans certaines applications, il est important de contrôler la pression exercée par le ramasseur d'ordures. Ainsi, allouer quelques millions de tuples inutilement peut être une mauvaise chose dans ces applications. Bien sûr, comme pour toutes les questions de performance, ne faites pas de changements aveugles avant d'avoir compris l'ampleur du problème.

2 votes

Renvoyer deux valeurs est souvent un substitut à l'absence de types d'options ou d'ADT.

0 votes

@Eric : C'est à propos d'un LoadBalancer qui renvoie l'adresse du serveur approprié ( string url) et la durée de validité de cette adresse de serveur ( int minutes). Pensez-vous qu'il y a un problème de conception ? (Note : Actuellement, j'utilise out mot-clé)

0 votes

Eric, cette question me turlupine depuis un moment, y a-t-il de bons cas d'utilisation pour le type Tuple ?

37voto

dimlucas Points 3105

Pour compléter les réponses précédentes, C# 7 apporte des tuples de type valeur, contrairement à System.Tuple qui est un type de référence et offrent également une sémantique améliorée.

Vous pouvez toujours les laisser sans nom et utiliser la fonction .Item* la syntaxe :

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42

Mais ce qui est vraiment puissant dans cette nouvelle fonctionnalité est la possibilité d'avoir des tuples nommés. Nous pourrions donc réécrire ce qui précède comme ceci :

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

La déstructuration est également prise en charge :

(string firstName, string lastName, int age) = getPerson()

2 votes

Ai-je raison de penser que cela renvoie essentiellement une structure avec des références comme membres sous le capot ?

4 votes

Savons-nous comment les performances de cette méthode se comparent à celles de l'utilisation de paramètres externes ?

21voto

Andrew Cooper Points 21126

Je pense que la réponse dépend de la sémantique de ce que fait la fonction, et de la relation entre les deux valeurs.

Par exemple, le TryParse Les méthodes prennent un out pour accepter la valeur analysée, et renvoyer un paramètre bool pour indiquer si l'analyse syntaxique a réussi ou non. Les deux valeurs ne vont pas vraiment ensemble, donc, sémantiquement, il est plus logique, et l'intention du code est plus facile à lire, d'utiliser la balise out paramètre.

Si, toutefois, votre fonction renvoie les coordonnées X/Y d'un objet à l'écran, les deux valeurs sont sémantiquement liées et il est préférable d'utiliser une balise struct .

J'éviterais personnellement d'utiliser un tuple pour tout ce qui sera visible par le code externe en raison de la syntaxe maladroite pour récupérer les membres.

0 votes

+1 pour la sémantique. Votre réponse est plus appropriée pour les types de référence, lorsque nous pouvons laisser l'attribut out paramètre null . Il existe quelques types immuables nullables.

3 votes

En fait, les deux valeurs de TryParse vont très bien ensemble, bien plus que ce qu'implique le fait d'avoir l'une comme valeur de retour et l'autre comme paramètre ByRef. À bien des égards, la valeur logique à retourner serait un type nullable. Il y a des cas où le modèle TryParse fonctionne bien, et d'autres où il est pénible (c'est bien qu'il puisse être utilisé dans une instruction "if", mais il y a beaucoup de cas où retourner une valeur nullable ou être capable de spécifier une valeur par défaut serait plus pratique).

0 votes

@supercat je suis d'accord avec andrew, ils ne vont pas ensemble. Bien qu'ils soient liés, le retour vous indique si vous devez vous préoccuper de la valeur et non quelque chose qui doit être traité en tandem. Ainsi, après avoir traité le retour, il n'est plus nécessaire de procéder à d'autres traitements relatifs à la valeur de sortie, ce qui est différent du retour d'une paire clé-valeur à partir d'un dictionnaire où il y a un lien clair et permanent entre la clé et la valeur. Je suis d'accord que si les types nullables avaient été dans .Net 1.1, ils les auraient probablement utilisés car null serait une façon correcte de signaler l'absence de valeur.

2voto

JeanP Points 21

Vous n'avez pas mentionné une autre option, qui consiste à avoir une classe personnalisée au lieu de la structure. Si les données ont une sémantique associée qui peut être exploitée par des fonctions, ou si la taille de l'instance est suffisamment grande (> 16 octets en règle générale), une classe personnalisée peut être préférée. L'utilisation de "out" n'est pas recommandée dans les API publiques en raison de son association avec les pointeurs et de la nécessité de comprendre le fonctionnement des types de référence.

https://msdn.microsoft.com/en-us/library/ms182131.aspx

Tuple est bon pour un usage interne, mais son utilisation est maladroite dans l'API publique. Donc, mon vote est entre struct et class pour l'API publique.

1 votes

Si un type existe uniquement dans le but de retourner une agrégation de valeurs, je dirais qu'un simple type de valeur à champ exposé est le plus adapté à cette sémantique. S'il n'y a rien d'autre dans le type que ses champs, il n'y aura aucune question sur les types de validation de données qu'il effectue (aucune, évidemment), s'il représente une vue capturée ou en direct (une structure à champs ouverts ne peut pas agir comme une vue en direct), etc. Les classes immuables sont moins pratiques à utiliser et ne présentent un avantage en termes de performances que si les instances peuvent être transmises plusieurs fois.

2voto

Sumit Points 757

J'opterai pour l'approche qui consiste à utiliser le paramètre Out, car dans la seconde approche, il faudrait créer un objet de la classe Tuple, puis lui ajouter une valeur, ce qui, à mon avis, est une opération coûteuse par rapport au renvoi de la valeur dans le paramètre Out. Cependant, si vous voulez renvoyer plusieurs valeurs dans la classe Tuple (ce qui, en fait, ne peut pas être accompli en renvoyant un seul paramètre out), je choisirai la deuxième approche.

0 votes

Je suis d'accord avec out . En outre, il y a un params mot-clé que je n'ai pas mentionné pour garder la question droite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X