web-dev-qa-db-fra.com

Renvoyer deux valeurs, Tuple vs 'out' vs 'struct'

Considérons une fonction qui renvoie deux valeurs. Nous pouvons é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?

74
Xaqron

Ils ont chacun leurs avantages et leurs inconvénients.

Les paramètres de sortie sont rapides et bon marché mais nécessitent que vous passiez une variable et que vous vous reposiez sur une mutation. Il est presque impossible d'utiliser correctement un paramètre de sortie avec LINQ.

Les tuples font pression sur la collecte et ne se documentent pas. "Item1" n'est pas très descriptif.

Les structures personnalisées peuvent être lentes à copier si elles sont volumineuses, mais elles sont auto-documentées et sont efficaces si elles sont petites. Cependant, il est également difficile de définir tout un tas de structures personnalisées pour des utilisations triviales.

Je serais enclin à la solution de structure personnalisée toutes choses étant égales par ailleurs. Encore mieux, c'est créer une fonction qui ne renvoie qu'une seule valeur. Pourquoi renvoyez-vous deux valeurs en premier lieu?

MISE À JOUR: Notez que les tuples en C # 7, qui ont été expédiés six ans après la rédaction de cet article, sont des types de valeur et donc moins susceptibles de créer une pression de collecte.

84
Eric Lippert

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, les méthodes TryParse prennent un paramètre out pour accepter la valeur analysée et renvoient un bool pour indiquer si l'analyse a réussi ou non. Les deux valeurs ne vont pas vraiment ensemble, donc, sémantiquement, cela a plus de sens, et l'intention du code est plus facile à lire, pour utiliser le paramètre out.

Si, cependant, votre fonction renvoie les coordonnées X/Y d'un objet à l'écran, alors les deux valeurs appartiennent sémantiquement et il serait préférable d'utiliser un struct.

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

19
Andrew Cooper

En plus des 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 offre également une sémantique améliorée.

Vous pouvez toujours les laisser sans nom et utiliser le .Item* 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é, c'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 restructuration est également prise en charge:

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

15
dimlucas

Vous n'avez pas mentionné une autre option, qui a une classe personnalisée au lieu de struct. Si les données sont associées à une sémantique 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 l'API publique en raison de son association à des pointeurs et nécessitant une compréhension du 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 délicate dans l'API publique. Donc, mon vote est entre struct et class pour l'API publique.

1
JeanP

J'irai avec l'approche de l'utilisation du paramètre Out parce que dans la deuxième approche, vous auriez besoin de créer et d'objet de classe Tuple, puis d'y ajouter de la valeur, ce qui, je pense, est une opération coûteuse par rapport au retour de la valeur du paramètre out. Bien que si vous souhaitez renvoyer plusieurs valeurs dans la classe de tuple (ce qui ne peut pas être accompli en renvoyant simplement un paramètre de sortie), je vais opter pour la deuxième approche.

1
Sumit

Il n'y a pas de "meilleure pratique". C'est ce avec quoi vous êtes à l'aise et ce qui fonctionne le mieux dans votre situation. Tant que vous êtes cohérent avec cela, il n'y a aucun problème avec les solutions que vous avez publiées.

0
foxy