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.
The Sample Solution
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.
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:
Tuple Assignment and Equality (C# 7.3)
Just like other variables, tuples can be assigned, provided the value types and amounts match:
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:
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.
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 exceptSystem.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:
Happy Coding!