As well as finally seeing the RTM of the .NET Core tooling, Visual Studio 2017 brought a whole host of new things to the table. Among these is C# 7.0, which introduces a number of new features to the language.
Many of these features are essentially syntactic sugar over things that were already possible, but were harder work or more cumbersome in earlier versions of the language. Tuples feels like one of these features that I'm going to end up using quite a lot.
Deconstructing tuples
Often you'll find that you want to return more than one value from a method. There's a number of ways you can achieve this currently (out
parameters, System.Tuple
, custom class) but none of them are particularly smooth. If you really are just returning two pieces of data, without any associated behaviour, then the new tuples added in C# 7 are a great fit.
I won't go into much detail on tuples here, so I suggest you checkout one of the many recent articles introducing the feature if they're new to you. I'm just going to look at one of the associated features of tuples - the ability to deconstruct them.
In the following example, the method GetUser()
returns a tuple consisting of an integer
and a string
:
(int id, string name) GetUser()
{
return (123, "andrewlock");
}
If I call this method from my code, I can access the id
and name
values by name - so much cleaner than out
parameters or the Item1
, Item2
of System.Tuple
.
Another feature is the ability to automatically deconstruct the tuple values into separate variables. So for example, I could do:
(var userId, var username) = GetUser();
Console.WriteLine($"The user with id {userId} is {username}");
This creates two variables, an integer
called userId
and a string
called username
. The tuple has been automatically deconstructed into these two variables.
Deconstructing non-tuples
This feature is great, but it is actually not limited to just tuples - you can add deconstructors to all your classes!
The following example shows a User
class with a deconstructor that returns the FirstName
and LastName
properties:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
}
With this in place I can deconstruct any User
object:
var user = new User
{
FirstName = "Joe",
LastName = "Bloggs",
Email = "joe.bloggs@example.com",
Age = 23
};
(var firstName, var lastName) = user;
Console.WriteLine($"The user's name is {firstName} {lastName}");
// The user's name is Joe Bloggs
We are creating a User
object, and then deconstructing it into the firstName
and lastName
variables, which are declared as part of the deconstruction (they don't have to be declared inlcline, you can use existing variables too).
To create a deconstructor, create a function of the following form:
public void Deconstruct(out T var1, ..., out T2 var2);
The values that are produced are declared as out
parameters. You can have as many arguments as you like, the caller just needs to provide the correct number of variables when calling the deconstructor. You can even have multiple overloads with different numbers of parameters:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
public void Deconstruct(out string firstName, out string lastName, out int age)
{
firstName = FirstName;
lastName = LastName;
age = Age;
}
}
The same user could be deconstructed in multiple ways, depending on the needs of the caller:
(var firstName1, var lastName1) = user;
(var firstName2, var lastName2, var age) = user;
Ambiguous overloads
One thing that might cross your mind is what happens if you have multiple overloads with the same number of parameters. In the following example I add an additional deconstructor also accepts three parameters, where the third parameter is a string rather than an int:
public partial class User
{
// remainder of class as before
public void Deconstruct(out string firstName, out string lastName, out string email)
{
firstName = FirstName;
lastName = LastName;
email = Email;
}
}
This code compiles, but if you try and actually deconstruct the object you'll get some red squigglies:
At first this seems like it's just a standard C# type inference error - there are two candidate method calls so you need to disambiguate between them by providing explicit types instead of var
. However, even explicitly declaring the type won't clear this one up:
You'll still get the following error:
The call is ambiguous between the following methods or properties: 'Program.User.Deconstruct(out string, out string, out int)' and 'Program.User.Deconstruct(out string, out string, out string)'
So make sure not to overload multiple Deconstruct
methods in a type with the same numbers of parameters!
Bonus: Predefined type 'System.ValueTuple`2' is not defined or imported
When you first start using tuples, you might get this confusing error:
Predefined type 'System.ValueTuple`2' is not defined or imported
But don't panic, you just need to add the System.ValueTuple NuGet package to your project, and all will be good again:
Summary
This was just a quick look at the deconstruction feature that came in C# 7.0. For a more detailed look, check out some of the links below:
- https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/
- http://www.davidhayden.me/blog/tuples-csharp-7-visual-studio-2017
- https://www.thomaslevesque.com/2016/08/23/tuple-deconstruction-in-c-7/
- https://visualstudiomagazine.com/articles/2017/01/01/tuples-csharp-7.aspx