Coding, Tech and Developers Blog
Today's article will focus on tuples and show examples of tuples in C# being a code smell. But also, I want to point out when tuples can actually help you make your code more readable and cleaner. The trick is not to mistake tuples for the hammer for every screw out there. Let me show you how.
There comes a time in many software developer careers in .NET (and various other languages as well) when people discover tuples as the general tool to write their code. I tend to find that true, especially in people's very early career stages (and I am certainly guilty of that myself) when Clean Code is still something that needs to be cut from the only roughly-shaped developer.
But first, let's quickly have a look at what a tuple is. We can think of it as a simple semantic grouping of values inside our code:
(double, int) myTuple = (4.9, 15);
// or simpler
var myTuple = (4.9, 15);
Accessing the "properties" of this tuple can be done using automatically generated accessors:
var temp = myTuple.Item1;
These accessors can be renamed in favor of more speaking terminology, like so:
(double MyDouble, int MyInt) myTuple = (4.9, 15);
var temp = myTuple.MyDouble;
That's it. Well, not quite, but you might want to look the details up in Microsoft's own documentation.
Looks like a small class, doesn't it?
There is a rather frequent refactoring that is being made to methods that have an out
parameter in their signature. Consider an example in which some code tries to validate a Customer
class. You might come across a method like this:
bool IsValid(Customer customerToValidate, out string reason);
How did this come into being? At first, probably, returning true
or false
for the validation seemed to be enough until one day, the business needed additional information to be displayed somewhere. As an easy fix, the developer tasked with this job added the out
parameter to this method. Not beautiful, but did the job, I guess.
As the business rules get more complicated, consider we'd have to make this method asynchronous, maybe an async call to a repository needs to be made within. Whatever the reason, out
parameters are not possible in asynchronous methods. Consequently, someone might have done the obvious: Introduce a tuple.
Task<(bool IsValid, string Reason)> IsValidAsync(Customer customerToValidate);
The fact that we have the ability to name the values inside of tuples (which came with C# 7), actually does not make this code bad. Personally, I still consider using tuples on these occasions to be code smell and there are a couple of reasons for it:
My favorite way of doing a refactoring to this method is to use a small record:
public async Task<ValidationResult> IsValidAsync(Customer customerToValidate)
{
...
}
public record ValidationResult(bool IsValid, string Reason);
This small change helps future me easily identify what the data are about. And if more properties are necessary, the method signature and calls do not change.
All that being said, I am not a fan of tuples and try to avoid them generally. There are however situations, where tuples can help you write very readable code.
Say that we wanted to calculate a specific discount for our customers based on the following rules:
If we implement these rules right away, we could end up with something like this:
public decimal CalculateDiscount(Customer customer, DateTime time)
{
if (customer.CustomerType == CustomerType.Gold)
{
return 0.3m;
}
else if (customer.CustomerType == CustomerType.Premium)
{
return 0.20m;
}
else
{
if (time.Hour is > 15 and < 20)
{
return 0.15m;
}
if (customer.Age < 25)
{
return 0.1m;
}
else
{
return 0m;
}
}
return 0m;
}
That is almost 30 lines of nested boilerplate code. Of course, we could easily bring that down to only a couple of lines by reducing the number of brackets and unnecessary else statements. But still, if we stick to this kind of information flow, we are still left with code that is not easily readable, especially considering that the business rules are pretty simple. They are likely to change or grow, and then this piece of code will get ugly in no time.
But here is the thing: If we make use of the fact, that tuples can be pattern matched, we might end up with this:
public decimal CalculateDiscount(Customer customer, DateTime time)
{
return (IsStudent(customer), IsHappyHour(time), customer.CustomerType) switch
{
(_, _, CustomerType.Gold) => 0.3m,
(_, _, CustomerType.Premium) => 0.2m,
(_, true, CustomerType.Regular) => 0.15m,
(true, false, CustomerType.Regular) => 0.10m,
(false, false, CustomerType.Regular) => 0.0m
};
}
public bool IsStudent(Customer customer) => customer.Age < 25;
public bool IsHappyHour(DateTime datetime) => datetime.Hour is > 15 and < 20;
Doesn't this pretty much look like the business rules stated above? In this case, creating a temporary tuple holding the necessary information and feeding it into C#'s pattern-matching engine reduced the complexity of our code significantly. The business rules can be directly read from our code and are easily extendable. Please also note that I made use of the discard (_
) variable for cases where only part of the tuple is actually relevant for the business decision.
There is quite a debate out there on whether tuples in C# should be considered a code smell. Some of the public discussions are old and some are newer. Without engaging in any of these discussions directly, I personally tend to avoid tuples in general. But I also admit that there are certain situations in which they are helpful. So, let me share my general recommendations:
public
access modifiers or even in interfaces.Let's hear from you:
Be the first to know when a new post was released
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.