NDepend Blog

Improve your .NET code quality with NDepend

C# ValueTuple Full Guide

January 18, 2024 9 minutes read

C# ValueTuple

In 2017 C# 7.0 introduced ValueTuple. This feature makes it convenient to handle lightweight data structures. Here is an example of relying on the C# ValueTuple parenthesis syntax  () to handle a simple Person type.

ValueTuples are available in all .NET Core versions (.NET 8, .NET 7…), in .NET Standard 2.0, and in .NET 4.7 and onward.

Using ValueTuple in Various Coding Scenarios

Tuple is a concept that comes from functional language. ValueTuples were introduced in C# to simplify code in a wide range of scenarios including:

The present article covers all aspects of ValueTuple that a C# developer needs to know to use them effectively.

The Generic ValueTuple Structures

Concretely ValueTuples are:

  • 8 generic structures in the namespace System: ValueTuple<T1>, ValueTuple<T1,T2>  …  ValueTuple<T1,T2,T3,T4,T5,T6,T7,TRest>
  • The C# ValueTuple parenthesis syntax  () introduced in the preceding sections leads the compiler to rely on these structures.

The choice of structure over class has been driven by performance. Instances of structures are lightweight: they are values stored on the thread stack or within an object’s binary layout. Thus these values are not tracked by the Garbage Collector at runtime, hence the substantial performance gain.

These structures have a field named ItemX for each generic type TX:

Normally a field with a public visibility is an anti-pattern and a property is preferred for encapsulation purposes. But ValueTuple is all about performance and since properties involve extra getter and setter method calls, this public-field design has been favored. A consequence is that an instance of a ValueTuple<…> structure is mutable: this means that its state can be changed just by assigning a ItemX field.

Another point to consider is comparison: since ValueTuples are value types we get value equality: two tuples are considered equals if all their fields are considered equals one by one:

The Named Items C# Syntax Over ValueTuples

C# lets the developer rename each ItemX field to make the code more readable. Here is a compilable code sample:

Notice how the 3x types…

  • ValueTuple<string, string, int>
  • (string FirstName, string LastName, int BirthYear)
  • (string fn, string ln, int by)

…are considered equivalent by the compiler. However, for each variable person1, person2 or person3, the C# compiler keeps track of the items’ names. For example, person3 is typed as (string fn, string ln, int by). Replacing person3.fn with person3.FirstName provokes a compiler error.

Named Item C# Syntax for Method Parameters and Result

You can use the Value Tuples named items syntax in C# for parameters and method results:

In the code sample above, notice that person1.BirthYear is left untouched by the call to IncreaseBirthYear(). This is because ValueTuple<..>  is a structure and not a class so it is passed by-value to the method IncreaseBirthYear(). You can still use the ref  keyword to pass that value person1 by-reference through a managed pointer and be able to reflect state changes performed in the method IncreaseBirthYear().

Decompiling the C# Named Items Syntax

Interestingly enough, if we decompile the first named item syntax code sample, we can see that the compiled IL code doesn’t keep a trace of ItemX fields’ name in source code.

named member ValueTuple C# syntax decompiled

However, when ValueTuples with named items are publicly visible, the compiler stores the fields’ names in the assembly’s metadata, allowing client assemblies to harness these names. The screenshot below shows a solution with a console project referencing a library project. The library project has a method GetMethod() that returns this named ValueTuple: (string firstName, string lastName, int birthYear). This method, called from the console project, gathers the person value through a var local variable. In the console project, you can still use the field names firstName, lastName, and birthYear, even if this project doesn’t mention them anywhere

named member ValueTuple C# syntax decompiled 2

The C# Value Tuple Parentheses Syntax

The named items syntax can be simplified with just a parentheses syntax. For example (bool success, int result) can be simplified as (bool, int) which exactly means ValueTuple<bool, int>  with no named items.

Furthermore, starting with C# 8.0, you can use pattern matching to test and/or deconstruct one or several items of a ValueTuple in a single expression. In the code sample below the Parse(…) method returns a ValueTuple (bool, int). The expression if(Parse(str) is (true, int result)) tests the boolean value and deconstructs the integer item into a local variable

The code sample above shows one of the most common uses of tuple: a lightweight way to obtain multiple values from a method call. Lightweight here has 2 meanings:

  • There is no need to declare a new class or a structure to type the method result. The concise syntax (bool, int) is enough.
  • The Garbage Collector doesn’t keep track of the value returned because (bool, int) is a structure.

The Many Facets of ValueTuple

ValueTuple and Deconstruction

The expression if(Parse(str) is (true, int result)) mixes pattern matching and deconstruction, but you can fully deconstruct a ValueTuple this way:

Thanks to deconstruction we obtain two local variables from a single expression. However, a structure ValueTuple<…> doesn’t have a Deconstruct(…) method. Instead, the C# compiler has a special behavior: it reads the item fields to assign the local variables. Thus the IL code looks like this:

ValueTuple and Pattern Matching

We saw that the expression if(Parse(str) is (true, int result)) uses pattern matching to test a single item of a ValueTuple. However one can also test multiple items at once this way:

ValueTuple and Hash Tables

Another common usage of ValueTuples is in hash tables like HashSet<T> or Dictionary<TKey, TValue> when the key or the value might be represented by multiple items. Here is an example:

Of course ValueTuples are also useful for collections that are not hash tables, like List<(bool,int)>.

ValueTuple and async methods

Async methods cannot have out parameters. Thus ValueTuple is an ideal way to return multiple items from an asynchronous call:

ValueTuple and the params keyword

It is worth noting that the keyword params works well with ValueTuples which makes it well suited to write methods that accept multiple key-value pairs like this one:

ValueTuple and Serialization

To serialize ValueTuples using System.Text.Json, you must use the serialization option IncludeFields = true, because the items in value tuples are fields, not properties:

Value tuples were once serializable through BinaryFormatter, but binary serialization has been discarded in .NET 5.0.

ValueTuple vs. Its Alternatives

ValueTuple vs. Tuple

Since .NET 4.0 we had the 8 Tuple classes:  Tuple<T1>, Tuple<T1,T2>  (…)  Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>

However, these tuple classes have two limitations leading to the introduction of the ValueTuple generic structures later:

  • Tuple, as classes, don’t fit well in lightweight scenarios like method return values. The Garbage Collector tracks each instance, which is not performance-wise.
  • Additionally, you cannot change the value of items in a Tuple because they are immutable

In most real-world scenarios you will favor ValueTuple structures over tuple classes. Let’s notice the ToValueTuple() method presented by each Tuple<…> class:

ValueTuple vs. Anonymous Type

C# 3.0 introduced anonymous types to be used in LINQ queries. Here is an example:

The expression new { Readable = $”{duration:c}”, duration.Ticks } forces the C# compiler to create this anonymous class in the compiled assembly.

As Tuple<…>, anonymous types are classes and their properties are immutable. Moreover, the compiler declares anonymous classes as internal. Thus they are not visible outside their projects. This means that in most scenarios ValueTuples represent a better choice than anonymous types because they are lightweight.

However, notice that anonymous type can be used in expression tree advanced scenarios (when IQueryable<T> is required) and ValueTuple cannot.

ValueTuple vs. Record Struct

Having to declare the tuple in method return type as in…

  • (bool, int) Parse(string str) {…
  • or in (bool success, int result) Parse(string str) {…

…can turn tedious when this syntax applies to many methods (say 5+ methods). In such a situation, it is preferable to write your custom lightweight record struct type such as:

Also, notice that ValueTuples don’t work with type alias. One cannot write:

Advanced ValueTuple

The ITuple interface

Each ValueTuple<…> structure implements the interface System.Runtime.CompilerServices.ITuple defined as:

Being declared in the namespace System.Runtime.CompilerServices is an indication you shouldn’t use this interface in your user code. This is because Length and the indexer are implemented as explicit interfaces in ValueTuple<…> structure. This implies that to use it, one must cast the tuple as an ITuple reference.

Casting a structure value to a reference means boxing. Boxing is when the runtime creates an object to host a structure value. The Garbage Collector’s management of this object degrades performance. By decompiling the sample code above we can see the box IL instruction.

The ValueTuple Structure

TheSystem.ValueTuple structure proposes several static methods like Create(), Equals() or GetHashCode(). Most of the time you won’t use those methods explicitly because the C# syntax for ValueTuple is more concise. For example, let’s compare the syntaxes to create a tuple:

Maybe you’ll want to use the method Create() to obtain a tuple with no item that might be useful in some algorithms that rely on ITuple to process any kind of tuple. Such empty tuple is an instance of the ValueTuple not-generic structure.

ValueTuple With More Than 8 Items

We saw that a ValueTuple<…> structure has at most 8 items. However in the structure ValueTuple<T1,T2,T3,T4,T5,T6,T7,TRest> the generic type TRest can be typed as a ValueTuple<…> that contains additional items. C# can accept ValueTuples with more than 8 items. The compiler provides special support for it under the hood. In the code below both ValueTuple are instances of ValueTuple<int,int,int,int,int,int,int,ValueTuple<int,int,int,int,int>>, but their length is different depending on how they get instantiated:

Conclusion

ValueTuple in C# stands out as a significant enhancement, streamlining code complexity with its performance-oriented and lightweight architecture. Its design makes it a more advantageous choice over traditional options in numerous situations.

The C# and .NET teams invested a lot of effort in ValueTuples to make them work seamlessly with other features such as deconstruction, pattern matching, async method, collection, hash table, or JSON serialization. If you are not using ValueTuples yet in your code, we hope that this article convinced you to change your habits and write better C# code with them.

Comments:

  1. Hey Patrick,

    thank you so much for this overview. It’s awesome!
    I enjoy every article you post!

    Just a small remark:
    I think that in the first code block the line 14
    “var dico = new List”
    should be replaced with
    “var dico = new Dictionary”
    to match the object initialization.

    Cheers
    Julian

  2. Fixed!
    Thanks Julian for the kind words, I am glad this content can help

Comments are closed.