How to use tuples with named fields in C# by example

Tuples are a very useful feature of the C# language; they allow more than one value to be grouped and passed back and forth without needing to define additional types.

As we’ll discover later in this article, tuples are particularly convenient for returning more than one value from a method, avoiding the need for ugly out parameters.

In newer versions of C#, tuples with named fields are available as an enhancement of the standard tuple functionality. As the name suggests, this allows you to name the fields that hold values within the tuple structure.

While tuples with named fields are very helpful in making code more readable, the syntax can be somewhat difficult to remember if you don’t use the feature all the time. Therefore, it’s useful to have some sample code that you can refer to whenever you find the need to use tuples.

This article shows by way of example the most common things you’ll typically need to do with tuples.

Standard tuples

Before looking at tuples with named fields, let me provide you with a quick introduction to (or reminder of) how the ‘standard tuples’ found in the System namespace work.

Standard tuples have been available since .NET Framework 4.0 and were implemented as classes in the BCL. This is important to remember since this means they are types first and foremost and not a language feature.

Let’s take a look at how the standard tuple types are created and used.

Creation

Standard tuples are created with the usual generic class constructor syntax, as follows.

var logEntry = new Tuple<DateTime, string>(DateTime.UtcNow, "Application started");

The Tuple class has generic type parameters that allow for strong typing of the components/properties that make up the tuple.

In the above example, a ‘log entry’ object is simulated by specifying the first component of the tuple as a DateTime to act as the timestamp and the second as a string to hold the log message.

The values of the tuple components must be passed into the constructor as arguments. There is no parameterless constructor.

Usage

The values contained within the tuple are accessed via properties that are named Item1, Item2, etc.

Console.WriteLine("{0}: {1}", logEntry.Item1, logEntry.Item2);
 
// Example output: 11/01/2022 21:46:52: Application started

Standard tuples provide a lot of flexibility, but the resulting code isn’t overly readable. This is particularly true if you happen to have 7 or 8 items in your tuple.

Note that the maximum number of items you can specify in a standard tuple is 8 (without workarounds).

Let’s move on to look at how tuples with named fields can help improve this situation.

Value tuples

C# 7.0 introduced support for a new type of tuple; the value tuple. Since then, tuples are available as a language feature whereby the compiler performs lots of magic underneath, allowing for syntactic sugar that makes tuples much nicer to work with.

Value tuples can have named fields which greatly improves code readability by allowing precise names to be used instead of the ‘Item’ properties that are in place for standard tuples.

Note that the newer tuples feature uses the System.ValueTuple type (a mutable struct) underneath, rather than System.Tuple (a class). This has implications for things like equality and performance (e.g. it avoids the penalty associated with allocating memory on the heap).

Creation

Creating tuples with named fields can be accomplished as follows.

var namedLogEntry = (timestamp: DateTime.UtcNow, message: "Application started");

As you can see from the above example, creating tuples with named fields is a very simple operation and is highly readable. However, it can be easy to forget the exact syntax unless you are using the feature frequently.

The key things to note about the syntax are as follows.

  • The new keyword is not used.
  • The overall components are surrounded by round brackets.
  • Individual components are separated by commas.
  • Colons are used to separate the names from the values.

If you can at least remember the round brackets and colons then you should be able to recreate named tuples from memory when required!

As an alternative to using var you can use the following syntax to spell out the tuple type in the source code.

(DateTime timestamp, string message) namedLogEntry = (DateTime.UtcNow, "Application started");

Note that if you leave out the field names and only specify the types i.e. (DateTime, string) namedLogEntry = ... the code will compile but you’ll be back in a situation where you need to refer to the tuple fields as Item1 and Item2 again.

As per other types, you can insert a question mark to make the tuple nullable, as follows.

(DateTime timestamp, string message)? namedLogEntry = null;

This can be useful in cases where you don’t want to specify the initial values and are planning to set them later instead based on specific conditions.

Usage

Tuples with named fields are much nicer to use compared to standard tuples.

Instead of Item1 and Item2 we can now access the items by their names.

Console.WriteLine("{0}: {1}", namedLogEntry.timestamp, namedLogEntry.message);

Note that individual opinions may vary on whether the names of the tuple components should use camelCasing or PascalCasing. I’m of the view that tuples are intended to be a ‘bag of variables’ and therefore tuple components should be named like normal variables. However, you should feel free to use the naming convention that makes the most sense to you.

Method return values

One of the best use cases for tuples is for allowing more than one value to be returned from a method.

To achieve this without tuples you could use out parameters. However, the resulting code is not very pretty, both at the method level and in regards to the code that is calling the method.

Alternatively, you could create a class with public properties. However, sometimes this can feel a bit cumbersome when you are simply seeking to return some primitive values.

Method definition

Below is an example of how to define and implement a method that returns multiple values using named tuples.

internal (DateTime timestamp, string message) GetLogEntry()
{
    return (DateTime.UtcNow, "Application started");
}

As you can see, the method definition is very clear and returning the values is also very straightforward.

Method invocation

After we call the GetLogEntry method we can use the tuple that is returned in the same manner as the previous examples, as shown below.

var logEntry = GetLogEntry();
 
Console.WriteLine("{0}: {1}", logEntry.timestamp, logEntry.message);

Again, we can either use the var keyword, as shown above, or we can spell out the precise tuple type returned by the method, where it helps to increase readability.

Deconstruction

When calling a method that returns a tuple, another approach is to ‘deconstruct’ the values that are returned.

var (timestampmessage= GetLogEntry();
 
Console.WriteLine("{0}: {1}", timestampmessage);

In the above example, timestamp and message are declared and initialised as separate variables and can be used directly as shown in the Console.WriteLine method call.

This is another very useful aspect of the tuples feature and makes the code very concise and easy to reason about.

When to use

Tuples, and particularly tuples with named fields are a very useful language feature. However, that doesn’t mean that they should be used everywhere in your application.

For a public API, you should favour using your own class or struct as the return type in place of a tuple.

Tuples are best kept to usage in private or internal methods where the underlying implementation can be changed without affecting the code that is consuming the public API.

Example use cases

One example use case could be where you are using a lightweight ORM like Dapper to execute a dynamic scalar query that is only selecting a couple of columns from a database table. In this scenario, you may wish to populate a dynamic object or set of objects and return these in a tuple format to avoid the need to create a separate class that maps to the query results.

Here’s a short example of what this would look like.

public async Task<(DateTime timestamp, string message)> GetLogEntryAsync(string deviceId)
{
    var logEntry = await _connection.QuerySingleOrDefaultAsync(
        "SELECT TOP(1) Timestamp, Message " +
        "FROM LogEntries " +
        "WHERE DeviceId = @DeviceId",
        new { DeviceId = deviceId });
 
    return (logEntry.Timestamp, logEntry.Message);
}

Note that the above code assumes an open IDbConnection is held by the _connection variable. Of course, you’d also need the Dapper NuGet package installed and a database table with a matching schema definition for the above code to work.

Depending on the nature of your specific application, you’ll need to determine when to use features like tuples or dynamic variables by weighing up simplicity, performance and other such considerations.

Over and out

This article introduced the concept of tuples, specifically tuples with named fields and expounded their benefits compared to standard tuples by way of example.

The syntax to use when creating, returning, and deconstructing tuples is not complex but it can be easily forgotten. I’ve included some tips in this article to help you remember how to create and consume tuples.

There are several different ways of using tuples in C# and I’ve shown you some of the variations so that you can choose the style that you prefer.

I’ve also talked about when and when not to use tuples and I’ve provided a practical example to act as a demonstration.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments