# C# tuple types

C# tuples are types that you define using a lightweight syntax. The advantages include a simpler syntax, rules for conversions based on number (referred to as cardinality) and types of elements, and consistent rules for copies, equality tests, and assignments. As a tradeoff, tuples do not support some of the object-oriented idioms associated with inheritance. You can get an overview in the section on tuples in the What's new in C# 7.0 article.

In this article, you'll learn the language rules governing tuples in C# 7.0 and later versions, different ways to use them, and initial guidance on working with tuples.

Note

The new tuples features require the ValueTuple types. You must add the NuGet package System.ValueTuple in order to use it on platforms that do not include the types.

This is similar to other language features that rely on types delivered in the framework. Examples include async and await relying on the INotifyCompletion interface, and LINQ relying on IEnumerable<T>. However, the delivery mechanism is changing as .NET is becoming more platform independent. The .NET Framework may not always ship on the same cadence as the language compiler. When new language features rely on new types, those types will be available as NuGet packages when the language features ship. As these new types get added to the .NET Standard API and delivered as part of the framework, the NuGet package requirement will be removed.

Let's start with the reasons for adding new tuple support. Methods return a single object. Tuples enable you to package multiple values in that single object more easily.

The .NET Framework already has generic Tuple classes. These classes, however, had two major limitations. For one, the Tuple classes named their properties Item1, Item2, and so on. Those names carry no semantic information. Using these Tuple types does not enable communicating the meaning of each of the properties. The new language features enable you to declare and use semantically meaningful names for the elements in a tuple.

The Tuple classes cause more performance concerns because they are reference types. Using one of the Tuple types means allocating objects. On hot paths, allocating many small objects can have a measurable impact on your application's performance. Therefore, the language support for tuples leverages the new ValueTuple structs.

To avoid those deficiencies, you could create a class or a struct to carry multiple elements. Unfortunately, that's more work for you, and it obscures your design intent. Making a struct or class implies that you are defining a type with both data and behavior. Many times, you simply want to store multiple values in a single object.

The language features and the ValueTuple generic structs enforce the rule that you cannot add any behavior (methods) to these tuple types. All the ValueTuple types are mutable structs. Each member field is a public field. That makes them very lightweight. However, that means tuples should not be used where immutability is important.

Tuples are both simpler and more flexible data containers than class and struct types. Let's explore those differences.

## Named and unnamed tuples

The ValueTuple struct has fields named Item1, Item2, Item3, and so on, similar to the properties defined in the existing Tuple types. These names are the only names you can use for unnamed tuples. When you do not provide any alternative field names to a tuple, you've created an unnamed tuple:

var unnamed = ("one", "two");


The tuple in the previous example was initialized using literal constants and won't have element names created using tuple field name projections in C# 7.1.

However, when you initialize a tuple, you can use new language features that give better names to each field. Doing so creates a named tuple. Named tuples still have elements named Item1, Item2, Item3 and so on. But they also have synonyms for any of those elements that you have named. You create a named tuple by specifying the names for each element. One way is to specify the names as part of the tuple initialization:

var named = (first: "one", second: "two");


These synonyms are handled by the compiler and the language so that you can use named tuples effectively. IDEs and editors can read these semantic names using the Roslyn APIs. You can reference the elements of a named tuple by those semantic names anywhere in the same assembly. The compiler replaces the names you've defined with Item* equivalents when generating the compiled output. The compiled Microsoft Intermediate Language (MSIL) does not include the names you've given these elements.

Beginning with C# 7.1, the field names for a tuple may be provided from the variables used to initialize the tuple. This is referred to as tuple projection initializers. The following code creates a tuple named accumulation with elements count (an integer), and sum (a double).

var sum = 12.5;
var count = 5;
var accumulation = (count, sum);


The compiler must communicate those names you created for tuples that are returned from public methods or properties. In those cases, the compiler adds a TupleElementNamesAttribute attribute on the method. This attribute contains a TransformNames list property that contains the names given to each of the elements in the tuple.

Note

Development Tools, such as Visual Studio, also read that metadata, and provide IntelliSense and other features using the metadata field names.

It is important to understand these underlying fundamentals of the new tuples and the ValueTuple type in order to understand the rules for assigning named tuples to each other.

## Tuple projection initializers

In general, tuple projection initializers work by using the variable or field names from the right-hand side of a tuple initialization statement. If an explicit name is given, that takes precedence over any projected name. For example, in the following initializer, the elements are explicitFieldOne and explicitFieldTwo, not localVariableOne and localVariableTwo:

var localVariableOne = 5;
var localVariableTwo = "some text";

var tuple = (explicitFieldOne: localVariableOne, explicitFieldTwo: localVariableTwo);


For any field where an explicit name is not provided, an applicable implicit name is projected. There is no requirement to provide semantic names, either explicitly or implicitly. The following initializer has field names Item1, whose value is 42 and stringContent, whose value is "The answer to everything":

var stringContent = "The answer to everything";
var mixedTuple = (42, stringContent);


There are two conditions where candidate field names are not projected onto the tuple field:

1. When the candidate name is a reserved tuple name. Examples include Item3, ToString. or Rest.
2. When the candidate name is a duplicate of another tuple field name, either explicit or implicit.

These conditions avoid ambiguity. These names would cause an ambiguity if they were used as the field names for a field in a tuple. Neither of these conditions cause compile-time errors. Instead, the elements without projected names do not have semantic names projected for them. The following examples demonstrate these conditions:

var ToString = "This is some text";
var one = 1;
var Item1 = 5;
var projections = (ToString, one, Item1);
// Accessing the first field:
Console.WriteLine(projections.Item1);
// There is no semantic name 'ToString'
// Accessing the second field:
Console.WriteLine(projections.one);
Console.WriteLine(projections.Item2);
// Accessing the third field:
Console.WriteLine(projections.Item3);
// There is no semantic name 'Item1.

var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);

var xCoords = (pt1.X, pt2.X);
// There are no semantic names for the fields
// of xCoords.

// Accessing the first field:
Console.WriteLine(xCoords.Item1);
// Accessing the second field:
Console.WriteLine(xCoords.Item2);


These situations do not cause compiler errors because that would be a breaking change for code written with C# 7.0, when tuple field name projections were not available.

## Equality and tuples

Beginning with C# 7.3, tuple types support the == and != operators. These operators work by comparing each member of the left argument to each member of the right argument in order. These comparisons short-circuit. They will stop evaluating members as soon as one pair is not equal. The following code examples use ==, but the comparison rules all apply to !=. The following code example shows an equality comparison for two pairs of integers:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // displays 'true'


There are several rules that make tuple equality tests more convenient. Tuple equality performs lifted conversions if one of the tuples is a nullable tuple, as shown in the following code:

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple); // Also true


Tuple equality also performs implicit conversions on each member of both tuples. These include lifted conversions, widening conversions, or other implicit conversions. The following examples show that an integer 2-tuple can be compared to a long 2-tuple because of the implicit conversion from integer to long:

// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true

// converted type of left is (long, long)
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple); // Also true

// comparisons performed on (long, long) tuples
(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Also true


The names of the tuple members do not participate in tests for equality. However, if one of the operands is a tuple literal with explicit names, the compiler generates warning CS8383 if those names do not match the names of the other operand. In the case where both operands are tuple literals, the warning is on the right operand as shown in the following example:

(int a, string b) pair = (1, "Hello");
(int z, string y) another = (1, "Hello");
Console.WriteLine(pair == another); // true. Member names don't participate.
Console.WriteLine(pair == (z: 1, y: "Hello")); // warning: literal contains different member names


Finally, tuples may contain nested tuples. Tuple equality compares the "shape" of each operand through nested tuples as shown in the following example:

(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );


It's a compile time error to compare two tuples for equality (or inequality) when they have different shapes. The compiler won't attempt any deconstruction of nested tuples in order to compare them.

## Assignment and tuples

The language supports assignment between tuple types that have the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. Other conversions aren't considered for assignments. It's a compile time error to assign one tuple to another when they have different shapes. The compiler won't attempt any deconstruction of nested tuples in order to assign them. Let's look at the kinds of assignments that are allowed between tuple types.

Consider these variables used in the following examples:

// The 'arity' and 'shape' of all these tuples are compatible.
// The only difference is the field names being used.
var unnamed = (42, "The meaning of life");
var anonymous = (16, "a perfect square");
var named = (Answer: 42, Message: "The meaning of life");
var differentNamed = (SecretConstant: 42, Label: "The meaning of life");


The first two variables, unnamed and anonymous do not have semantic names provided for the elements. The field names are Item1 and Item2. The last two variables, named and differentName have semantic names given for the elements. These two tuples have different names for the elements.

All four of these tuples have the same number of elements (referred to as 'cardinality') and the types of those elements are identical. Therefore, all of these assignments work:

unnamed = named;

named = unnamed;
// 'named' still has fields that can be referred to
Console.WriteLine($"{named.Answer}, {named.Message}"); // unnamed to unnamed: anonymous = unnamed; // named tuples. named = differentNamed; // The field names are not assigned. 'named' still has // fields that can be referred to as 'answer' and 'message': Console.WriteLine($"{named.Answer}, {named.Message}");

// With implicit conversions:
// int can be implicitly converted to long
(long, string) conversion = named;


Notice that the names of the tuples are not assigned. The values of the elements are assigned following the order of the elements in the tuple.

Tuples of different types or numbers of elements are not assignable:

// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;


## Tuples as method return values

One of the most common uses for tuples is as a method return value. Let's walk through one example. Consider this method that computes the standard deviation for a sequence of numbers:

public static double StandardDeviation(IEnumerable<double> sequence)
{
// Step 1: Compute the Mean:
var mean = sequence.Average();

// Step 2: Compute the square of the differences between each number
// and the mean:
var squaredMeanDifferences = from n in sequence
select (n - mean) * (n - mean);
// Step 3: Find the mean of those squared differences:
var meanOfSquaredDifferences = squaredMeanDifferences.Average();

// Step 4: Standard Deviation is the square root of that mean:
var standardDeviation = Math.Sqrt(meanOfSquaredDifferences);
return standardDeviation;
}


Note

These examples compute the uncorrected sample standard deviation. The corrected sample standard deviation formula would divide the sum of the squared differences from the mean by (N-1) instead of N, as the Average extension method does. Consult a statistics text for more details on the differences between these formulas for standard deviation.

The preceding code follows the textbook formula for the standard deviation. It produces the correct answer, but it's an inefficient implementation. This method enumerates the sequence twice: Once to produce the average, and once to produce the average of the square of the difference of the average. (Remember that LINQ queries are evaluated lazily, so the computation of the differences from the mean and the average of those differences makes only one enumeration.)

There is an alternative formula that computes standard deviation using only one enumeration of the sequence. This computation produces two values as it enumerates the sequence: the sum of all items in the sequence, and the sum of the each value squared:

public static double StandardDeviation(IEnumerable<double> sequence)
{
double sum = 0;
double sumOfSquares = 0;
double count = 0;

foreach (var item in sequence)
{
count++;
sum += item;
sumOfSquares += item * item;
}

var variance = sumOfSquares - sum * sum / count;
return Math.Sqrt(variance / count);
}


This version enumerates the sequence exactly once. But it's not reusable code. As you keep working, you'll find that many different statistical computations use the number of items in the sequence, the sum of the sequence, and the sum of the squares of the sequence. Let's refactor this method and write a utility method that produces all three of those values. All three values can be returned as a tuple.

Let's update this method so the three values computed during the enumeration are stored in a tuple. That creates this version:

public static double StandardDeviation(IEnumerable<double> sequence)
{
var computation = (Count: 0, Sum: 0.0, SumOfSquares: 0.0);

foreach (var item in sequence)
{
computation.Count++;
computation.Sum += item;
computation.SumOfSquares += item * item;
}

var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
return Math.Sqrt(variance / computation.Count);
}


Visual Studio's Refactoring support makes it easy to extract the functionality for the core statistics into a private method. That gives you a private static method that returns the tuple type with the three values of Sum, SumOfSquares, and Count:

public static double StandardDeviation(IEnumerable<double> sequence)
{
(int Count, double Sum, double SumOfSquares) computation = ComputeSumAndSumOfSquares(sequence);

var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
var computation = (count: 0, sum: 0.0, sumOfSquares: 0.0);

foreach (var item in sequence)
{
computation.count++;
computation.sum += item;
computation.sumOfSquares += item * item;
}

return computation;
}


The language enables a couple more options that you can use, if you want to make a few quick edits by hand. First, you can use the var declaration to initialize the tuple result from the ComputeSumAndSumOfSquares method call. You can also create three discrete variables inside the ComputeSumAndSumOfSquares method. The final version is shown in the following code:

public static double StandardDeviation(IEnumerable<double> sequence)
{
var computation = ComputeSumAndSumOfSquares(sequence);

var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count;
return Math.Sqrt(variance / computation.Count);
}

private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
double sum = 0;
double sumOfSquares = 0;
int count = 0;

foreach (var item in sequence)
{
count++;
sum += item;
sumOfSquares += item * item;
}

return (count, sum, sumOfSquares);
}


This final version can be used for any method that needs those three values, or any subset of them.

The language supports other options in managing the names of the elements in these tuple-returning methods.

You can remove the field names from the return value declaration and return an unnamed tuple:

private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
double sum = 0;
double sumOfSquares = 0;
int count = 0;

foreach (var item in sequence)
{
count++;
sum += item;
sumOfSquares += item * item;
}

return (sum, sumOfSquares, count);
}


The fields of this tuple are named Item1, Item2, and Item3. It's recommended that you provide semantic names to the elements of tuples returned from methods.

Another idiom where tuples can be useful is when you are authoring LINQ queries. The final projected result often contains some, but not all, of the properties of the objects being selected.

You would traditionally project the results of the query into a sequence of objects that were an anonymous type. That presented many limitations, primarily because anonymous types could not conveniently be named in the return type for a method. Alternatives using object or dynamic as the type of the result came with significant performance costs.

Returning a sequence of a tuple type is easy, and the names and types of the elements are available at compile time and through IDE tools. For example, consider a ToDo application. You might define a class similar to the following to represent a single entry in the ToDo list:

public class ToDoItem
{
public int ID { get; set; }
public bool IsDone { get; set; }
public DateTime DueDate { get; set; }
public string Title { get; set; }
public string Notes { get; set; }
}


Your mobile applications may support a compact form of the current ToDo items that only displays the title. That LINQ query would make a projection that includes only the ID and the title. A method that returns a sequence of tuples expresses that design well:

internal IEnumerable<(int ID, string Title)> GetCurrentItemsMobileList()
{
return from item in AllItems
where !item.IsDone
orderby item.DueDate
select (item.ID, item.Title);
}


Note

In C# 7.1, tuple projections enable you to create named tuples using elements, in a manner similar to the property naming in anonymous types. In the above code, the select statement in the query projection creates a tuple that has elements ID and Title.

The named tuple can be part of the signature. It lets the compiler and IDE tools provide static checking that you are using the result correctly. The named tuple also carries the static type information so there is no need to use expensive run time features like reflection or dynamic binding to work with the results.

## Deconstruction

You can unpackage all the items in a tuple by deconstructing the tuple returned by a method. There are three different approaches to deconstructing tuples. First, you can explicitly declare the type of each field inside parentheses to create discrete variables for each of the elements in the tuple:

public static double StandardDeviation(IEnumerable<double> sequence)
{
(int count, double sum, double sumOfSquares) = ComputeSumAndSumOfSquares(sequence);

var variance = sumOfSquares - sum * sum / count;
return Math.Sqrt(variance / count);
}


You can also declare implicitly typed variables for each field in a tuple by using the var keyword outside the parentheses:

public static double StandardDeviation(IEnumerable<double> sequence)
{
var (sum, sumOfSquares, count) = ComputeSumAndSumOfSquares(sequence);

var variance = sumOfSquares - sum * sum / count;
return Math.Sqrt(variance / count);
}


It is also legal to use the var keyword with any, or all of the variable declarations inside the parentheses.

(double sum, var sumOfSquares, var count) = ComputeSumAndSumOfSquares(sequence);


You cannot use a specific type outside the parentheses, even if every field in the tuple has the same type.

You can deconstruct tuples with existing declarations as well:

public class Point
{
public int X { get; set; }
public int Y { get; set; }

public Point(int x, int y) => (X, Y) = (x, y);
}


Warning

You cannot mix existing declarations with declarations inside the parentheses. For instance, the following is not allowed: (var x, y) = MyMethod();. This produces error CS8184 because x is declared inside the parentheses and y is previously declared elsewhere.

### Deconstructing user-defined types

Any tuple type can be deconstructed as shown above. It's also easy to enable deconstruction on any user-defined type (classes, structs, or even interfaces).

The type author can define one or more Deconstruct methods that assign values to any number of out variables representing the data elements that make up the type. For example, the following Person type defines a Deconstruct method that deconstructs a person object into the elements representing the first name and last name:

public class Person
{
public string FirstName { get; }
public string LastName { get; }

public Person(string first, string last)
{
FirstName = first;
LastName = last;
}

public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}


The deconstruct method enables assignment from a Person to two strings, representing the FirstName and LastName properties:

var p = new Person("Althea", "Goodwin");
var (first, last) = p;


You can enable deconstruction even for types you did not author. The Deconstruct method can be an extension method that unpackages the accessible data members of an object. The example below shows a Student type, derived from the Person type, and an extension method that deconstructs a Student into three variables, representing the FirstName, the LastName, and the GPA:

public class Student : Person
{
public double GPA { get; }
public Student(string first, string last, double gpa) :
base(first, last)
{
GPA = gpa;
}
}

public static class Extensions
{
public static void Deconstruct(this Student s, out string first, out string last, out double gpa)
{
first = s.FirstName;
last = s.LastName;
gpa = s.GPA;
}
}


A Student object now has two accessible Deconstruct methods: the extension method declared for Student types, and the member of the Person type. Both are in scope, and that enables a Student to be deconstructed into either two variables or three. If you assign a student to three variables, the first name, last name, and GPA are all returned. If you assign a student to two variables, only the first name and the last name are returned.

var s1 = new Student("Cary", "Totten", 4.5);
var (fName, lName, gpa) = s1;


You should be careful defining multiple Deconstruct methods in a class or a class hierarchy. Multiple Deconstruct methods that have the same number of out parameters can quickly cause ambiguities. Callers may not be able to easily call the desired Deconstruct method.

In this example, there is minimal chance for an ambiguous call because the Deconstruct method for Person has two output parameters, and the Deconstruct method for Student has three.

Deconstruction operators do not participate in testing equality. The following example generates compiler error CS0019:

Person p = new Person("Althea", "Goodwin");
if (("Althea", "Goodwin") == p)
Console.WriteLine(p);


The Deconstruct method could convert the Person object p to a tuple containing two strings, but it is not applicable in the context of equality tests.

## Conclusion

The new language and library support for named tuples makes it much easier to work with designs that use data structures that store multiple elements but do not define behavior, as classes and structs do. It's easy and concise to use tuples for those types. You get all the benefits of static type checking, without needing to author types using the more verbose class or struct syntax. Even so, they are most useful for utility methods that are private, or internal. Create user-defined types, either class or struct` types when your public methods return a value that has multiple elements.