We are chugging right along in our C# in Simple Terms mega-series. In the last few posts, we've talked about LINQ basics, generics, and arrays and collections.

In this post, let's take a look at one of the newest additions to C# (tuples) and its similar-but-different counterpart, anonymous types.

Do you know how long it took me to find an "anonymous" picture without the Guy Fawkes mask? Photo by Du Wei / Unsplash

The Sample Solution

exceptionnotfound/CSharpInSimpleTerms
Contribute to exceptionnotfound/CSharpInSimpleTerms development by creating an account on GitHub.
Projects for this post: 16Tuples and 16AnonymousTypes

A Quick Note

Tuples became available in C# 7.0. A few of the features in this article are from later versions of C#; where this happens, it will be noted. Check the link below for the default C# version used by versions of the .NET Framework.

C# language versioning - C# Guide
Learn about how the C# language version is determined based on your project and the reasons behind that choice. Learn how to override the default manually.

Tuples (C# 7.0)

A tuple is a group of values gathered together in a simple structure. Unlike a collection such as List<T>, a tuple is immutable and of a fixed size.

(double, int) myTuple = (8.2, 6);

C# provides a way to get values out of a tuple as though they are properties of a class:

Console.WriteLine(myTuple.Item1); //8.2
Console.WriteLine(myTuple.Item2); //6

We can also provide names for the values inside the tuple:

(double Average, int Min, int Max) secondTuple = (4.5, 2, 17);
Console.WriteLine("Average: " + secondTuple.Average.ToString()
                    + ", Max: " + secondTuple.Max.ToString()
                    + ", Min: " + secondTuple.Min.ToString());

Why Use Tuples?

The most common use case for tuples is as a return type from a method. This is particularly useful if the method would otherwise return a class which might only be used in this one instance.

For example, let's say we have a method to get basic statistics from a list of integers:

public class Statistics
{
    public double Average { get; set; }
    public int Min { get; set; }
    public int Max { get; set; }
    
    public static Statistics GetStats(List<int> values)
    {
        Statistics stats = new Statistics();
        stats.Average = values.Average();
        stats.Min = values.Min();
        stats.Max = values.Max();

        return stats;
    }
}

static void Main(string[] args)
{
    List<int> values = new List<int> { 6, 2, 7, 9, 2, 5, 3, 8, 10, 6 };

    var stats = Statistics.GetStats(values);

    Console.WriteLine("Average: " + stats.Average
                      + ", Max: " + stats.Max
                      + ", Min: " + stats.Min);
}

This code can be reduced using tuples:

public static (double Average, int Min, int Max) GetTupleStats(List<int> values)
{
    return (values.Average(), values.Min(), values.Max());
}

static void Main(string[] args)
{
    List<int> values = new List<int> { 6, 2, 7, 9, 2, 5, 3, 8, 10, 6 };

    var stats = GetStats(values);

    Console.WriteLine("Average: " + stats.Average
                        + ", Max: " + stats.Max
                        + ", Min: " + stats.Min);
}

Tuples also allows us to replace out parameters from methods and can be used in place of anonymous types, which are defined later in this post. We talked about out parameters and methods in a previous post:

C# in Simple Terms - Methods, Parameters, and Arguments
Let’s make some methods, pass some parameters, and accept some arguments!

Tuple Assignment and Equality (C# 7.3)

Just like other variables, tuples can be assigned, provided the value types and amounts match:

(decimal, int) myTuple1 = (5.67M, 4);
(decimal pricePerUnit, int units) myTuple2 = (1.1M, 1);

myTuple2 = myTuple1;

Console.WriteLine("Price per Unit: $" + myTuple2.pricePerUnit
                  + ", Units: " + myTuple2.units);
Output: "Price per Unit: $5.67, Units: 4"

As of C# 7.3, we can also compare tuples using the == and != operators.

(string, int) person1 = ("John Smith", 45);
(string name, int age) person2 = ("Kayla Johnson", 52);

Console.WriteLine(person1 == person2); //False
Console.WriteLine(person1 != person2); //True

Note that tuples compare their values from left-to-right, so if the types of values in the same place don't match, we get a build error:

(string, int) person1 = ("John Smith", 45);
(int, string) person2 = (45, "John Smith");

Console.WriteLine(person1 == person2); //ERROR
Error: Operator '==' cannot be applied to operands of type 'string' and 'int'.

Further, if two tuples do not have the same number of values, the equality operator is short-circuited and we get a different build error.

(string, int, DateTime) person4 = ("John Smith", 45, new DateTime(1975, 1, 14));
Console.WriteLine(person1 == person4);
ERROR: Tuple types used as operands of an == or != operator must have matching cardinalities. But this operator has tuple types of cardinality 2 on the left and 3 on the right.

Tuple Deconstruction

One of the neater features in C# 7.0 and later is the ability to "deconstruct" tuples. Deconstructing tuples allows us to assign the values in the tuple to multiple individual variables.

var person = ("John Smith", 45, 50000.00M); //Create a tuple
(string name, int age, decimal salary) = person; //Deconstruct the tuple

Console.WriteLine(name + ", age " + age + " makes $" + salary + "/year.");

We can also use the var keyword outside the parentheses to let the compiler determine the variable types:

 var person = ("John Smith", 45, 50000.00M);
 var (name, age, salary) = person;

 Console.WriteLine(name + ", age " + age + " makes $" + salary + "/year.");

We can even use existing variables:

var person = ("John Smith", 45, 50000.00M);
string name;
int age;
decimal salary;
(name, age, salary) = person;

Console.WriteLine(name + ", age " + age + " makes $" + salary + "/year.");

Anonymous Types

Anonymous types are similar to tuples in many ways, but are used differently. We instantiate an anonymous type using the new and var operators:

var myAnonymous = new { Name = "John Smith", Age = 4 };

The most common scenario in which anonymous types are used is to select properties from another object or collection of objects, usually via LINQ:

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime DateOfBirth { get; set; }
}

var users = GetUsers(); //Method not defined here
var properties = users.Select(x => new { x.Name, x.Age });

As we mentioned in the LINQ Basics post, when we Select() properties, we create a projection; this projection is an anonymous type.

Anonymous types are read-only; once instantiated, their values cannot be changed. Further, anonymous types don't have a type in the traditional sense; their type is generated and assigned to them by the compiler. Consequently they come with many more restrictions than tuples:

  • Anonymous types cannot be used as parameters or return values.
  • Anonymous types may only have properties; constructors or other methods are not permitted.
  • Anonymous types derive from System.Object, and therefore cannot be cast to anything except System.Object.

Summary

Both tuples and anonymous types are groups of values, though they are used differently.

Tuples are groups of values with a defined size. They are most often used as return types from methods, but can also be used to replace out parameters and classes which might otherwise not be necessary.

Anonymous types are read-only groups of values. Anonymous types do not have a type in the traditional sense (it is assigned to them by the compiler); they cannot be used as parameter or return types; and they are most often used with LINQ queries to get projections of classes.

Got questions about tuples or anonymous types? I want to help! Ask in the comments below.

In the next part of this series, we will dive into ways we can associated metadata will classes and properties, and how we can look up that information later. That post is now available here:

C# in Simple Terms - Attributes and Reflection
Let’s learn how to store and retrieve metadata using attributes and reflection.

Happy Coding!