Why do we care for Tuple at all?
Do you like functions like TryParse
? Saying that
it is ugly is an understatement. Function is returning two values
bool success
and int number
but we are
getting one as a regular returned value and second one as out
parameter. This is not only inconsistent approach, it will also
not work for async
functions:
In situations like this people are using all kind of tricks like
returning Pair
or KeyValuePair
just to cut overhead of
defining a class or structure. Designing a class make sense
only if you are expressing design intent and need to add some
behaviour rather than just move data around.
Why System.Tuple in .NET 4 was not that great
Some languages are allowing returning multiple values (complex
result) and following that Microsoft provided something similar by
introducing System.Tuple
in .NET 4. Problem
with this solution is that member names are not customizable. They
are always Item1
, Item2
and so on.
Also, System.Tuple
is a reference type, therefore it
is heap allocated and garbage collected. That is not that bad if
you are doing more copying (assignment) than creation. Assuming
that main usage is complex function result, reference type is
unnecessarily stressing GC.
New C#7 feature and System.ValueTuple library to the rescue
With new C#7 feature and library defining generic structure System.ValueTuple
we got much more elegant solution for returning multiple values.
In order to use it you must include NuGet package
in your project. (Update Aug 15, 2017: Starting from .Net Core 2.0 you don't need to explicitly include ValueTuple package)
What is ValueTuple
? Think of it like an unnamed type.
Good news, you can give semantic name to a field instead using
predefined Item1, Item2,
..., but you cannot add any behaviour to it.
First let's clarify some terminology:
- tuple type -
(string, int)
- tuple literal -
("Big Hero", 6)
- tuple literal with named elements -
(name: "Big Hero, version: 6)
Creation
Let's create some tuples. Unnamed System.ValueTuple
is
similar to the old System.Tuple
except it is a value type
and will go on a stack. Since no names are provided, fields are
named as Item*
:
Using named tuple you can give semantic names to the fields
(although generic Item*
naming is still available):
You can provide names in the literal on the right side:
If you specify names on both sides, right ones will be ignored and you will get compiler warning:
Changing
Fields are public and mutable. Since System.ValueTuple
doesn't have any "logic"
that can put some constraints on data, making fields immutable would be useless.
Assignment
Assignment is possible if number of fields (called arity)
and types are the same. If types are not the same implicit conversion will be done. Field names are not assigned!
Values are assigned according to the order. For example:
(string, int)
and ValueTuple<string, int>
are "same type".
(string name, int version)
is not the "same type" but "convertible" to and from previous two.
Also (string, int)
could be converted to (string, double)
from example below:
Deconstruction
Finally, example how we can better write functions like TryParse
.
Divide
function from example below is returning complex result and
we can get result into one variable of type System.ValueTuple
or
deconstruct it to two variables:
But there is more! You can provide deconstruction for any type in .NET by implementing
Deconstruct
method as a member of a class:
Even if you can't change class you can make Deconstruct as an extension method:
If you have both defined (class and extension Deconstruct
) and both are with the same signature,
class method will be used.
While using deconstruction, you can explicitly declare field types or implicitly declare them
by using var
on the left side. You can even mix and have one var
only.
You can deconstruct to existing variables. Also, you can ignore some values by using "discards"
symbol "_"
:
You will like LINQ even more
With LINQ I'm abusing anonymous types even though they are not ideal solution.
Apart from being reference type, there is a huge limitation that anonymous type can not be returned
from the function. That is not the case with System.ValueTuple
. You can return
IEnumerable<>
of it and finally enjoy layering your LINQ queries in multiple functions:
Nullable it is
ValueTuple
is a value type so go ahead and make it nullable:
Still not baked to perfection
If you look at decompiled source you will find that names are just listed as synonyms in
TupleElementNames
attribute. Those are handled by compiler and Roslyn API within
the same assembly and will be replaced with Item*
once you cross that boundary.
Names will be also lost when casting to dynamic
or serializing to JSON.
Since names are stripped, having class with two properties with tuples different only by names
will generate compilation error.
Using System.ValueTuple in ASP.NET
I was hoping that I can get away with creating System.ValueTuple
model in MVC
but names are lost. Still you can decompose to something more readable:
MVC Controller
MVC View
Not a perfect solution since you rely on order! I'm sure names will cross this border in future language iterations.
(Update Aug 15, 2017: Starting from ASP.Net Core 2.0 you don't need extra step to include C#7 options for Razor)
In order to get C#7 features in MVC Core View you must include package:
While configuring services and adding MVC you must include C#7 options for Razor:
How it's made
We already know that names are just friendly synonyms stored in attribute. But how come we can define tuple with any number of elements? If you look at definition you will find that maximum number of fields is 7, but there is another field called Rest
. When you call Item8
in the background is emitted Rest.Item1
! After Rest.Item7
you will get Rest.Rest.Item1
:
That is probably all you need to know about System.ValueTuple
for now. I like it mainly because it helps me write less code.