1. Introduction
Let's imagine we have a function that, given a user (like by name), needs to return their age, registration date, and an activity flag all at once. In C and early versions of C#, it was totally normal for a function to return just one value. But what if you need to return more than one? That question gave birth to a bunch of different approaches, each with its own ups and downs.
In previous lectures, we've already met tuples. Now let's check out out-parameters and compare these two approaches.
The most popular approaches:
- Use out-parameters.
- Return some object with the needed fields (an anonymous type or a custom class).
- Return a tuple (ValueTuple).
In this lecture, we'll take a closer look at two of these approaches – out-parameters and tuples, and compare them head-to-head to figure out the perks of each for returning multiple values from a function.
2. out-parameters: a blast from the past
When you see a function like:
void GetUserInfo(string userName, out int age, out DateTime registrationDate, out bool isActive)
{
// calculations here
age = 42;
registrationDate = new DateTime(2010, 1, 1);
isActive = true;
}
you kinda wanna ask: is this a function or a mini car wash? So much stuff gets sent out, and all of it bypasses the main return value!
How it works
The calling code has to declare variables ahead of time, which the function will fill in:
int age;
DateTime reg;
bool isActive;
GetUserInfo("Bob", out age, out reg, out isActive);
// Now all the variables are filled
Console.WriteLine($"{age}, {reg}, {isActive}");
Cons of the out approach
- Harder to read: The method signature gets super wide, and the meaning of the returned data isn't always clear from the parameter names.
- Annoying for call chains: You can't easily plug this method into a chain (like, pass the result straight into another method).
- Mutates passed variables: The method has to modify existing variables; if you forget the out — you'll get a compile error.
- Initialization overhead: The compiler makes you declare outside variables, even if you only want to use the result once.
- Limited readability in big methods: If there are a bunch of out-parameters, it's easy to get lost about what's what and why you need it.
That's where tuples start to shine with their star power.
3. Tuples — the evolved approach
Let's compare with an example
Using a tuple:
public (int Age, DateTime RegistrationDate, bool IsActive) GetUserInfo(string userName)
{
// simulating a database lookup
return (42, new DateTime(2010, 1, 1), true);
}
Usage:
// Get all the values at once and give them variable names
var (age, regDate, isActive) = GetUserInfo("Bob");
Console.WriteLine($"{age}, {regDate}, {isActive}");
No extra declarations, no out, everything's super readable!
Comparison table: out vs tuple
| Criterion | Out-parameters | Tuples (ValueTuple) |
|---|---|---|
| Signature | long, out-parameters in the list | compact, everything comes back as one "package" |
| Usage | have to declare variables, then call the method | can deconstruct right away |
| Readability | often gets lost if there are several out-parameters | element names are clear right away |
| Easy to combine? | no | yep, you can nest and move them around |
Tuples VS. out — what's under the hood
With out-parameters, the function is actually working with memory outside its own "territory": to oversimplify, it's changing variables that were created somewhere else. You gotta be careful, because you can accidentally "mess up" the state of variables (thanks, compiler, for forcing assignments!).
A tuple, though, is a data structure that the function creates, fully initializes, and returns. So it's all one package, and you won't lose it on the way or forget about it.
Reading and maintaining code
Let's be real — when you're looking at someone else's code a week later, you'd rather see:
(var age, var city, var isActive) = GetUserInfo("Anna");
instead of
int age;
string city;
bool isActive;
GetUserInfo("Anna", out age, out city, out isActive);
Tuples make method signatures less noisy, and the result of using them is just way more intuitive.
4. Situations where tuples are especially awesome
1. When you need to return a result and an error message
public (bool Success, string ErrorMessage) TryProcess(string data)
{
if (string.IsNullOrEmpty(data))
return (false, "No data");
// process data...
return (true, "");
}
var (ok, error) = TryProcess(input);
if (!ok)
Console.WriteLine($"Error: {error}");
2. For functions that return multiple search results
public (User? FoundUser, int Index) FindUserByName(User[] users, string name)
{
for (int i = 0; i < users.Length; i++)
{
if (users[i].Name == name)
return (users[i], i);
}
return (null, -1);
}
3. For "pairs" of values: calculating min and max
public (int min, int max) FindMinMax(int[] numbers)
{
int min = numbers[0], max = numbers[0];
foreach (var n in numbers)
{
if (n < min) min = n;
if (n > max) max = n;
}
return (min, max);
}
var (minValue, maxValue) = FindMinMax(arr);
GO TO FULL VERSION