Tuples, tuples... value tuples!

Introduction

In the very first beginning of tuples in c# I firstly skipped it, because the readability had been not what I though of creating readable code.

Example from MSDN:

(bool, string) t1 = (true, "");
Console.WriteLine($"Tuple with value {t1.Item1} and string {t1.Item2}.");

When using tuples with more than 2 variables it was always about remembering which variable is which item.

With the introduction of ValueTuples this changed. Now it is possible to assign a 'name' to each tuple variable:

(bool rc, string error) t2 = (true, "");
Console.WriteLine($"Tuple with value {t2.rc} and string {t2.error}.");

Where for?

Now where should I use tuples?

Data storage

In general they can be used everywhere. However imho for complex data, the code readability will be worse, if you use it for storing big data instead of using classes. Let me show you an example:

(string FirstName, string LastName, DateTime BirthDay) person

Using it together with a list will result in:

List<(string FirstName, string LastName, DateTime BirthDay) person>

In this case using a dedicated data class should be prefered as the readability greatly improves and the data class can be extended with additional methods.

But for temporarily storing small data constructs, where Dictionaries, Collections or Hastables do not bring in any benefit, they are great also!

Method return type

That is the point where we switched to value tuples and now using them heavily. Instead of using out variables or ref variables for providing the error messages to the calling method we switched to tuples. Please note, that this is specific about requirements of returning an error - it is NOT a recommendation of when to use return values or Exceptions. See for example language agnostic - Which, and why, do you prefer Exceptions or Return codes? - Stack Overflow

Previously:

string error = "";
bool rc = FirstMethod(ref string error);
if(!rc)
  _logger.Error(error);

rc = SecondMethod(ref string error);
if(!rc)
  _logger.Error(error);

Now with tuples:

(bool success, string error) callRc = FirstMethod();
if(!callRc.success)
  _logger.Error(callRc.error);

callRc = SecondMethod();
if(!callRc.success)
  _logger.Error(callRc.error);

It can be easily extended with a more complex tuple for also handling an additional variable providing e. g. data from the called method which does work great together with nullable:

(bool success, string error, string[] lines) callRc = LoadFile();
if(!callRc.success)
{
   _logger.Error(callRc.error);
   return;
}
// process data using callRc.lines ...

var

Instead of specifying the return values each on it's own:

(bool success, string error) callRc = FirstMethod();

We can also use var which does work perfect with IntelliSense:

var callRc = FirstMethod();

Deconstruct

And last but not least, we can also 'desconstruct' the tuple and direct access the single variables:

(bool success, string error) = FirstMethod();
if(!success)
  _logger.Error(error);

// use them again but without declaration 
(success, error) = SecondMethod();
if(!success)
  _logger.Error(error);

Naming conventions

Internally we decided to use the following typing conventions with tuples:

  • For return tuples, we use tuple variables in lowerCase

  • For data tuples, we use tuple variables in UpperCase

Last but not least, please remind yourself that tuples are value types!