NDepend

Improve your .NET code quality with NDepend

C# ValueTuple

C# ValueTuple

C# 7.0 introduced Value Tuples which represent both a set of structures in the .NET Base Class Library (BCL) and some convenient C# syntax. Value tuples are available in all .NET Core versions, in .NET Standard 2.0 in and in .NET 4.7 and onward.

Tuple is a concept that comes from functional language and it was introduced in C# to simplify code in a wide range of scenarios including:

The present article is covering all aspects of value tuples that a C# developer needs to know to use them effectively.

The generic ValueTuple structures

There are 8 generic structures in the namespaces System that are: ValueTuple<T1>, ValueTuple<T1,T2>  (…)  ValueTuple<T1,T2,T3,T4,T5,T6,T7,TRest>

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 value tuple 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<…> structures is mutable: this means that its state can be changed just by assigning an Item field.

Another point to consider is comparison: since value tuples 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 value tuples

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 as equivalent by the compiler. However, for each reference 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

The syntax with named item can be used for parameter and method result:

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 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.

named member ValueTuple C# syntax decompiled

However when value tuples with named items are publicly visible, the fields’ names get stored in the assembly’s metadata so the names can be harnessed from client assemblies. 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 value tuple: (string firstName, string lastName, int birthYear). This method is called from the console project and the person value is gathered through a var local variable. We can see that the field’s names firstName, lastName and birthYear are still usable from the console project, despite being mentioned nowhere in this project.

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) that exactly means ValueTuple<bool,int>  with no named items.

Moreover since C# 8.0 pattern matching can be used to test and / or deconstruct one or several value tuples items in a single expression. In the code sample below the Parse(…) method returns a value tuple (bool,int). The expression if(Parse(str) is (true, int result)) test the boolean value and deconstructs the integer item into a local variable

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

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

Value Tuple and Deconstruction

The expression if(Parse(str) is (true, int result)) shows a mix of pattern matching and deconstruction but a value tuple can be fully deconstructed 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 items fields to assign the local variables. Thus the IL code looks like:

Value Tuple 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 value tuple. However multiple items can be tested at once:

Value Tuple and hash table

Another common usage of value tuples 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 value tuple are also useful for collections that are not hash tables, like List<(bool,int)>.

Value Tuple and async method

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

Value Tuple and the params keyword

It is worth to note that the keyword params works well with value tuples which makes it well suited to write methods that accepts multiple key value pairs like this one:

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 it in your user code. This is because Length and the indexer are implemented as explicit interface in ValueTuple<…> structure. This means that to be used, the tuple needs to be casted as a ITuple reference.

Casting a structure value to a reference means boxing. Boxing is when the runtime creates an object to host a structure value. This object gets tracked by the Garbage Collector and thus, performance gets hurt. By decompiling the sample code above we can see the box IL instruction.

The ValueTuple structure

The structure System.ValueTuple 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 value tuple is more concise, for example 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.

Value Tuple 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 value tuples with more than 8 items and provide special support for it under the hood. In the code below both value tuple 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:

Value Tuple and Serialization

Value tuples can be serialized through System.Text.Json but the serialize option IncludeFields = true  must be used since items are fields and not properties:

Value tuples used to be serializable through BinaryFormatter but binary serialization has been discarded in .NET 5.0.

Value Tuple 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>

The value tuple structures were introduced later because the tuple classes have 2 limitations:

  • Tuple don’t fit in lightweight scenarios like method return value. Tuples are classes and thus each instance created gets tracked by the Garbage Collector which is not performance wise.
  • Tuple are immutable, the items value cannot be changed.

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

Value Tuple 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 anonymous classes are declared as internal and are not visible outside their projects. This means that in most scenarios value tuples are better suited 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 value tuples cannot.

Value Tuple 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 become tedious if many methods are impacted (say 5+ methods). In such situation better write your custom lightweight record struct type such as:

Also notice that value tuples don’t work with type alias, one cannot write:

Conclusion

Value tuple is a bless because it can really simplify most C# code. Thanks to its performance-centric and lightweight design, value tuple is preferable over alternatives in most scenarios. The C# and .NET teams invested a lot of effort in value tuples 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 value tuples yet in your code, we hope that this article convinced you to change your habits and write better C# code with them.

My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.

I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly and also did manage some academic and professional courses on the platform and C#.

Over my consulting years I built an expertise about the architecture, the evolution and the maintenance challenges of large & complex real-world applications. It seemed like the spaghetti & entangled monolithic legacy concerned every sufficiently large team. As a consequence, I got interested in static code analysis and started the project NDepend in 2004.

Nowadays NDepend is a full-fledged Independent Software Vendor (ISV). With more than 12.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and full control on their application to a wide range of professional users around the world.

I live with my wife and our twin kids Léna and Paul in the beautiful island of Mauritius in the Indian Ocean.

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

Comments are closed.