System.Tuple
is a set of generic utility classes that lets a lazy developer get away without creating a separate class. At first sight it might look handy, but it isn’t. It’s a code smell. And Tuples behave the same as anything that smells: It gets worse if you leave it and let them spread throughout the code base.
Typical use of tuples include allowing multiple return values for functions or storing temporary data inside a method.
/// <summary> /// Recommended pressure of front and back tires of the car. /// </summary> public Tuple<double, double> TirePressure { get { return Tuple.Create(2.0, 2.4); } } /// <summary> /// Calculate the average of numbers in a sequence of sequences. /// </summary> /// <param name="values"></param> /// <returns></returns> public int AggregatedAverage(IEnumerable<IEnumerable<int>> values) { var firstLevel = values.Select(s => Tuple.Create(s.Average(), s.Count())); return firstLevel.Select(fl => fl.Item1 * fl.Item2).Sum() / firstLevel.Sum(fl => fl.Item2); } |
Neither of these make the code very readable, so what should be done instead?
Use Real Types for Return Types
A method’s return type is a core piece of the signature of the function. It is as important that it is understandable as it is that the method name is understandable. We’ve left the age of variables named a
, b
and c
so why should we accept a return type containing members Item1
and Item2
?
Creating a real class or struct to hold the return values is the only reasonable thing to do in this case.
public struct TirePressureInfo { readonly double front, back; public TirePressureInfo(double front, double back) { this.front = front; this.back = back; } public double PressureFront { get { return front; } } public double PressuerBack { get { return back; } } } public TirePressureInfo TirePressure { get { return new TirePressureInfo(2.0, 2.4); } } |
It’s substantially more code to write, but for a caller of the method it is now clear what the method returns without consulting documentation. Even if it is your own function, it’s far easier to mix up Item1
with Item2 than PressureFront
and PressureBack
. Creating a separate class is not the only option however. Consider a method that first returned the distance driven since the last refuel. Due to changed requirements it now returns the last three refuels.
/// <summary> /// Distance since last three refuels. /// </summary> public Tuple<int, int, int> KmSinceRefueling |
In this case the right thing to do is not to create a separate type, but rather to recognize it for what it is: a sequence of values.
public IEnumerable<int> KmSinceRefueling |
Intermediate Values
For intermediate values inside functions the case is even more simple than for return values: use an anonymous type.
public static double AggregatedAverage(IEnumerable<IEnumerable<int>> values) { var firstLevel = values.Select(s => new { Average = s.Average(), Count = s.Count() }); return firstLevel.Select(fl => fl.Average * fl.Count).Sum() / firstLevel.Sum(fl => fl.Count); } |
Totally Meaningless: Tuple<T1>
If I’ve been ranting about the smell of Tuple
in general so far, there is a case that has an especially stinking smell: Tuple<T1>
. What’s the point of it? It just contains one public property Item1
. Why not use the item directly instead?
Tuple’s Advantages
Laziness is an argument for using it. Not a good argument, but it is an argument.
Okay, I’ll give it in, there is an advantage of Tuple
classes. Not that they are better for my lazy fellow dev, but a real advantage that a custom quickly hacked together class will lack: Comparison. The Tuple
classes implement IStructuralEquatable
, IStructuralComparable
and IComparable
. Those are really basic things, but surprisingly easy to get wrong. And they increase the boiler plate coded needed for small data carrying class if it is to be used as a key to a Dictionary
.
I have a situation when I use Tuple and it does not look so bad. The example is with mapping two exemplar types : Employee and EmployeeHistroy into one EmployeeDto which is needed for the front end. I make a Tuple and create a map for AutoMapper Tuple => Employee. Wouldn`t it be overkill to make another type just to make this map? I wonder if there is a better and cleaner approach.
As long as you keep the usage of
Tuple
internal it should be fine, it’s when you start leaking it to the outside world it gets important with meaningful names. In your case I think that the most important thing is to keep an eye on it and make sure that it remains isolated in one place. Once it starts to get used as part of visible APIs (even if only internally) I think that it should be replaced with a specific type.Yes! Could not agree more, death to Tuples!