Méthode renvoyant plusieurs valeurs en C#
Il y a plusieurs manières en C# d'implémenter une méthode renvoyant plusieurs valeurs. En voici quelque-unes, avec autant d'information que possible sur chacunes d'entre-elles.
-
1
Utiliser une classe
Ce procédé, probablement le plus courant, est d'utiliser une classe comme valeur de retour. Vous êtes ainsi totalement libre de retourner ce que bon vous semble, puisque c'est vous qui définissez entièrement l'objet qui sera retourné.public sealed class MyReturnContainer { public Guid GuidValue { get; } public string StringValue { get; } public int IntValue { get; } public MyReturnContainer(Guid g, string s, int i){ GuidValue = g; StringValue = s; IntValue = i; } }
public class AnotherClassSomewhereInYourCode { public AnotherClassSomewhereInYourCode() { } public MyReturnContainer GetMultipleValues(){ return new MyReturnContainer(Guid.NewGuid(), "string value", 1); } }
Avantages :
- Flexibilité totale
- Supporte l'asynchronisme
Inconvénients :
- Les classes sont de type référence (moins performant que le type valeur)
- Si la classe n'a pas de vrai sens métier ou n'est utilisée qu'une seule fois, cela peut alourdir votre modèle inutilement
-
2
Utiliser les paramètres de sortie (out)
Les paramètres de sortie permettent de déclarer qu'une méthode a une, ou plusieurs valeurs de sortie en plus de sa valeur de retour.
Un paramètre out indique au compilateur que l'objet ne sera initialisé qu'à l'intérieur de la fonction, un paramètre out ne peut donc être qu'une valeur de sortie car sa valeur ne peut pas être initialisée en amont. D'autre part, une méthode ayant des paramètres out devra obligatoirement leur attribuer une valeur à tous. Une méthode ayant des paramètres out non-assignés engendrera une erreur de compilation.
Guid myGuid = Guid.Empty; string myString = string.Empty; int myInt = 0; public bool GetMultipleValues(out Guid g, out string s, out int i){ g = Guid.NewGuid(); s = "string value"; i = 1; return true; } bool ret = GetMultipleValues(out myGuid, out myString, out myInt); Console.WriteLine(myString); // "string value"
A noter qu'un paramètre out peut être nullable.
public bool TryCreateSomething(string param1, string param2, bool param3, out Guid? id){ id = null; // do something return true; }
Enfin, avec C# 7.0, les paramètres se sortie n'ont plus à être déclarées avant leur passage en paramètres mais peuvent l'être à la volée, comme ceci :
public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); Console.WriteLine($"({x}, {y})"); }
Avantages :
- Flexibilité
Inconvénients :
- Ne supporte pas l'asynchronisme
- Obligation d'assigner une valeur, même nulle
-
3
Utiliser les paramètres de sortie par référence (ref)
Passer un ou plusieurs paramètres par référence a pour effet que toute modification de valeur qui sera effectuée au sein d'une méthode sera répercutée sur la variable d'origine, en dehors de la méthode.
Contrairement aux paramètres de sortie (out), tout paramètre passé en référence à une méthode doit être préalablement initialisé mais ne doit pas forcemment voir une valeur lui être assignée par cette même méthode.
Un paramètre ref ayant une valeur avant-même l'exécution d'une méthode, on dit que c'est un paramètre double-sens (two-ways), car sa valeur peut être utilisée au sein d'une méthode, même si cette dernière ne lui a encore rien assigné.
string message = "Hello"; public void AlterMyMessage(ref string message){ message = "Hi there !"; } Console.WriteLine(message); // "Hello" AlterMyMessage(ref message); Console.WriteLine(message); // "Hi there !"
Avantages :
- Flexibilité
- Double-sens
Inconvénients :
- Ne supporte pas l'asynchronisme
-
4
Les Tuple
Apparus avec le Framework .NET 4.0, les Tuple permettent de créer des objets sur-mesures et complexes sans pour autant avoir à déclarer une classe. Ils peuvent contenir autant de paramètres que vous le souhaitez (le framework .NET pends en charge jusqu'à 7 éléments, mais vous pouvez outrepasser cette limitation en imbriquant des Tuple dans la propriété Rest d'un Tuple), de tout type. A leur utilisation, ces paramètres seront nommés ItemX, X étant leur position (Item1 pour le premier paramètre, Item2 pour le deuxième, etc).
class TupleExample { static void Main() { // Instanciation d'un Tuple à 3 paramètres Tuple<int, string, bool> tuple = new Tuple<int, string, bool>(10, "string value", true); // Accès aux propriétés du Tuple créé if (tuple.Item1 == 10) { Console.WriteLine(tuple.Item1); } if (tuple.Item2 == "string") { Console.WriteLine(tuple.Item2); } if (tuple.Item3) { Console.WriteLine(tuple.Item3); } } }
Techniquement, un Tuple est une classe qui fournit des méthodes statiques pour créer des instances.
Si cet objet s'avère extrêmement pratique et utile dans bon nombre de cas, il est à noter qu'il est tout de même à utiliser avec parcimonie, n'étant pas toujours facile à maintenir dans le temps. En effet, il est parfois difficile lorsqu'on repasse sur une méthode 2 ans après l'avoir écrite, de se souvenir à quoi correspondent les propriétés Item4, Item5 et Item6 ... Cela dit, il semble que ce ne sera pas une limitation encore longtemps, comme l'atteste cette issue GitHub. Qui plus est, si vous décidez d'ajouter un paramètre en deuxième position, alors les noms de tous les paramètres suivants seront impactés.
Avantages :
- Evite d'alourdir le modèle inutilement
- Flexibilité totale
- Supporte l'asynchronisme
Inconvénients :
- Dénomination abstraite des propriétés
- Maintenabilité
- Performance (type référence)
-
5
Utiliser une entrée de dictionnaire (KeyValuePair)
class KeyValuePairExample { static void Main() { KeyValuePair<int, string> kvp = GetKeyValuePair(); } KeyValuePair<int, string> GetKeyValuePair(){ return new KeyValuePair<int, string>(1, "string value"); } }
Avantages :
- Facilité d'utilisation (classe implémentant déjà certaines méthodes de requêtage, et autres)
- Supporte l'asynchronisme
- Unicicité des coupes clé/valeur (en cas de dictionnaire)
Inconvénients :
- Performance (moins performant qu'un Tuple de 2 paramètres, ou qu'une structure)
-
6
Utiliser une structure (struct)
Les structures sont des alternatives aux classes, et sont de type valeur, contrairement aux classes qui sont de type référence. Si les structures ont bien plus de limitations que les classes, elles sont bien plus performantes, raison pour laquelle il est préférable d'utiliser des structures quand vous le pouvez, surtout si la structure de données en question aura un volume d'instanciations important.
Principales limitations des structures par rapport aux classes :
- Héritage impossible
- Toutes les propriétés doivent avoir une valeur
- Les constructeurs sans paramètres sont impossibles (il avaient été ajouté dans C# 6 mais retirés depuis, et ne sont à priori pas à l'étude pour C# 7)
- Tout constructeur doit assigner une valeur à chaque propriété de la structure (sinon la valeur par défaut du type sera assignée)
public struct Point { public int x, y; // Constructor: public Point(int x, int y) { this.x = x; this.y = y; } // Override the ToString method: public override string ToString() { return(String.Format("({0},{1})", x, y)); } }
Avantages :
- Type valeur (performance++)
- Simplicité
- Supporte l'asynchronisme
Inconvénients :
- Rigidité de l'objet