As promised then, we're going to look at
C# tuples and I've actually decided I'm going to split this into two videos
because there's quite a lot of material to cover. So this time, we're just going
to look at the basic idea of tuples and then next time we'll move on to
things like deconstructors and discards, which are C# features
that rely very heavily on tuples. But before we look at anything too
technical, we've of course got to discuss the important matter:
how do you pronounce the word? Now as you've spotted, I pronounce it 'toople',
but you will hear people calling it 'tupple'. You'll even hear people calling
them 'tyooples', not that it matters at all. I call it a
'toople' because I say 'quintoople', though you will hear
people say 'quintupple', though i don't think you'd ever hear
anyone say 'quadrupple' It's normally 'quadroople', but it really doesn't matter. The real fun is going to be when i start doing
the subtitles for this video and have to work out how to express all those
phonetically [how was it?] But for now let's look at some code and let's look at the code that we left off with from last time, which was
using the slightly more old-fashioned approach of anonymous objects. So here
we've got an anonymous object. I've added a bit more information - added a 'Year' on from when we left it off last time - but basically we've got three properties. We've got a string 'FirstName', a string 'LastName' and we've got an int
'Year'. And we saw that because it's an anonymous object, the only way
we can pass it around is as an 'object', which means when we come out here and get hold of it, we've got no ability to select any
particular element. We can just do the basic things like we
can do a 'Console.Write' and it gives us a reasonably friendly
way of displaying that. So let's turn that into a tuple. Now this is a feature that came in in C# 7 and what
we're going to do is, instead of returning an 'object', we use
round brackets and to start with we'll just specify the
types which are going to be '(string, string,' and 'int)' - so that's what our first
name our last name and a year are going to be. And then we can return an object of this tuple type just with a similar syntax of
just separating things out with commas within - not curly braces as you have for anonymous types - but within
round brackets. And we can see immediately that's all perfectly happy. If we run it up we'll see we've got a slightly different way of doing the output: it's just giving us the
values, not the property names. But it's doing the same sort of thing. But, a little bit better, what we have now is I can say 'anon.' and what we get now is simply 'Item1', 'Item2' and 'Item3'. Not particularly friendly names - we'll come on and fix that - but 'Item1' we can see is of type string, 'Item3' we can see is of type int and if I just wanted to access the one of those then I can do that and we'll just get back 1797. So that's a big step forward. We've now
definitely got a type. The compiler understands the type. The compiler
enforces the type. If we were to try to do something like 'anon. Item3 =' and then try to assign a string into that it's going to give us compilation errors
because we've got type mismatches, so we're on a much better footing. We can
do even better than that actually, because we can give alternative names to those properties. So what I could also do here is, rather saying 'string, string, int' I could say 'string FirstName, string LastName,' and 'int Year'. You can see I can still set that up just by position, so '("Mary", "Shelley", 1797)'. But now we can see that when I look at 'anon' over here, it actually has 'FirstName', 'LastName' and 'Year' - so much more friendly names
for what's going on. So now I could put 'Year' in here and it would achieve the same thing, but
much clearer code to say 'Year' than to say 'Item3', and much clearer code to say 'FirstName' than to say 'Item1'. A thing you might have noticed there though, when I did have something like
'Item1', although the intellisense is not showing because it would really
rather we didn't use it, it is still there. So when you have one
of these tuples, you will always have 'Item1', 'Item2' up to however many you've gone for. Notice - irritates me a little bit it's
not 'Item0', it starts at 'Item1', but there you go - but they're still there; they still have what you'd expect, it's still of type
string. But it's much, much better, once you've given them names, to use those because it just makes the code so much clearer.
Now although a tuple is a language feature it's actually implemented largely as a generic and this is a trick that's been done
before in C#. When we have, for example, things like nullable value types. So if i do 'int? x', which is now allowed to take on a value 'null', that's actually just a shorthand for using the 'Nullable' generic, and really those two are exactly the
same thing. You can see it's greying that out to recommend that can be simplified by turning it into the question mark. But it's just a language feature that supports an
underlying generic and we've got that with these tuples. But there is a point of confusion because we actually have two similar sounding generics where we could have 'Tuple<string, string, int>' call that 't1' and we can also have 'ValueTuple<string, string, int>' and we'll call that 't2'. And the
difference between a 'Tuple' and a 'ValueTuple' is, if we look at the definition, we can see that the Tuple is a generic class, so it's a reference type, whereas the ValueTuple, as the name suggests, is a generic struct, so that's a value type. So the difference is in the nature of the tuple itself although obviously the elements within it - we can see there, strings and ints - can
be either value types or reference types in
both cases. And then we'd better just give those some initial values, so I'll
say that one equals 'null' because it's a reference type, that one '= default' because it's a value type. And then if i declare our language tuple, so that's going to be
just '(string, string, int)' and I call that 't3'. Then if I try saying 't3 = t1' and 't3 = t2', then we can see it's happy assigning in t2, but not assigning in t1, which tells
us that our language tuple, the new thing in C# 7, is actually an expression of the ValueTuple not the class Tuple - the reference Tuple - which has been actually around for rather longer. So it's an easy confusion to have there. Normally we just stick with the syntax of the C# language itself, but if
you do venture into using the generics, make sure that you use the right one. One
other thing to show you there - let's stick, then, with our ValueTuple - and when I start putting this together, you'll notice that it actually has eight
overloads - eight generic overloads. So we can have 'T1', 'T1, T2' ... goes all the way up there. So I could express something like - let's just do it
with ints - so one, two, three, four, five, six, seven - so I can go up to, in this case, seven parameters, but then we're going to hit a problem in that it's only been overloaded up to eight parameters. Now you could well argue - and I would - if you're using tuples for this many values then you probably should just write a class to do it anyway, but it's still a bit of a limitation. However, if we look at the intellisense we can see that that eighth parameter is not just another one of these standard types, it's what's called 'TRest'. And that basically means, in order
to get this beyond the eight, we can just put another tuple in there. So what we can do there is make that eighth parameter 'ValueTuple' and then we can just start off again all
the way up to 14, and then we'd have to do another one
because we'd have hit the limit again. And what you can see here is, with some very clever magic, it actually has flattened all that out. So we don't have to look at the eighth parameter and then subdivide it; we can see it's got items all the way up to 10 in there, just with this bit of slightly fancy work. Of course, again, it's easier to do it with the syntax, because the syntax - although it's working on that basis underneath the covers - doesn't actually
have that limitation. I can just keep putting those in there, and I can
just there say 't3' and you see we're getting the same sorts of results. So it's a bit cleverer, but it still only
covers having to use that TRest for you. But just a reason, as you'd probably expect, why it's better to use the syntax. Also, using the syntax is better because with the syntax we can very easily
actually name the individual properties, rather than having to just use 'Item1', 'Item2' and so on and so forth. So that's the basic idea behind what we
have with tuples. Now let's actually go back and look at the specific problem we were trying to solve last time with anonymous classes, which
didn't really work very well because what I've also got here is this web application. So if we set that as the startup project, and you may remember that in our BooksController here I had decided that on the Details I didn't want to have a predefined model specifying which features of a Book I wanted, I wanted just to use an anonymous type. So here, although a Book as we saw has all
these features: Title, Author, Publisher etc., I just wanted to be able to have the Id and the Title and the Blurb. And the
problem of doing that with an anonymous type was, although i can get the data out of there, because I don't have a name for the model type, when I go to the view I can't really specify anything in there. And actually, so as
this program is at the moment, it's going to crash when i try to go to Details. So I click Details and I just get told
there was a mismatch between the anonymous type I sent and the type
that I've specified in the view. So let's change that to using a tuple. So in here let's make the change here first. Let's say the model is simply going to be, we're going to have the 'int Id', we're going to have the 'string title' and we're going to have the 'string' and we have the 'Blurb' in there. So there's our model as a tuple. And then having done that we obviously no longer have 'Author' so we'll change that to our 'Blurb'- put our 'Blurb' in there. We also no longer have the Publisher, so we'll just delete that. And then back in our controller, what I'm going to do here is just that. And once again, just like with an anonymous type, because I've initialized this from properties of another object - Id, Title and Blurb - it works out that the names of the properties on the
tuple should be 'Id', 'Title' and 'Blurb'. So that works really neatly. We don't have to spend a lot of time specifying the type - we could do it, because it's going to be exactly the same as we've got here. So I could just have put that in there, but it's so much easier just to use 'var' and you get exactly the same result. And so now if we run that and hit Details we can see that's now working, except you'll have noticed this one horrible problem. Where we've gone for the property names, it's picked the 'Item1', 'Item2', 'Item3'. The reason being, if we look at our view, we've used 'DisplayNameFor' - and it
would be the same if you use the tag helper here. The problem is that actually the names of these properties on the tuple are a bit of a cheat. They're known to the compiler, but they're not known at run time and therefore when we do the DisplayNameFor, it just looks up 'Item1', 'Item2' or 'Item3'. So if you are going to do this there is a slight downside that for any of that metadata we'll need to type that in there for
ourselves. We can't do things like put a 'Display' attribute on there because you just can't put attributes on these properties. So we'd actually have to hard code that. But even so, that's what plenty of people do all the time anyway - it's not a huge effort. And then when we go to Details, it's now looking sensible. So that's using tuple to pass ad hoc data from the controller to the view - by creating a model pretty much on the fly, rather than having to write a model class. There is still one slight drawback there however. The example that we were just looking at was creating a single object, so we've done our '_context.Books.Single()' there. Let's do a similar sort of thing on our Index though. So previously we had a specific model, but what i'm going to do
is turn this into a tuple. So we'll get rid of that we'll just put round brackets and then we are going to have 'Id', 'Title' and then just like we had before, let's just do 'b.Blurb' on there. But even so, you can see that
it's objecting to that. If we hover over that you'll see it says: 'An expression tree may not contain a tuple literal.' Basically we're not allowed to use tuples with Entity Framework. We are allowed to use them with regular collections, and that gives us a simple
solution because what we could then do is, if we just do 'Books.ToList()', then that executes the query. So now we've got the data in memory and we are allowed to to do the tuple on that. You may be wondering, well, how could it tell the
difference? How could it give us a compiletime error on that sort of information? The thing to watch out for is that our 'Select()' on 'ToList()' is extending the IEnumerable interface,
whereas if we did it directly on 'Books' it was extending the IQueryable interface. And if you remember, actually I think it was my very first video,
on this difference between the way that we get our extension methods on IQueryable and IEnumerable - it again comes down to this. So that's how it knows and that's why
we need to do the ToList(). So that's going to work okay. We're going to need to change our view for this as well, just the same as we did before. So let's, in there, make that an IEnumerable' of
'(int Id, string Title, string Blurb)'. And then again on these 'DisplayNameFor's we're going to have to change that to 'Id', 'Title', 'Blurb'. And we'll delete that one because we've got one fewer. And then on here ,change that one to 'Blurb' and delete that one. And if we run that up then we'll see that we're getting our Id and our Title and our Blurb just as we expected. But there's still
one slight problem. If you remember back in the video when we're looking at anonymous classes, one of the things we saw there was that if you used an anonymous class to select a subset of all the columns you've got with our data, then it does some degree of optimization on the query so that it only fetches back those data items. But if we take a look here at the generated SQL, you can see that it's done a SELECT of Id, Author, Blurb, Publisher, PublisherAddress, Review, Count and Title in the query it sent. So it's pulled back all the data even though
we've only used a subset of it. And we can understand that if we look at the code, because back in the controller we can see here that. Because we said 'Books.ToList()' in order to get it to an IEnumerable - that is the point at which the query is executed; before we'd specified that we only want Id, Blurb and Title. So although it's nice to be able to use the anonymous type, because we can't use it with Entity Framework, we don't get the performance benefit. So the trick that we do is have effectively the best of both worlds. So
on our Books, on that we're going to say 'Select' and remember that is the IQueryable so we can use anonymous types. So i'll say here 'b =>' and then 'new {' and then into that I'm just going to take exactly the same three properties - because the syntax is similar apart from the fact it's curly
braces - and then leave the rest the same. So we are selecting on Entity Framework just Id, Title and Blurb, which we're allowed to do, but then having executed the query we then convert the anonymous type - which is no good because we can't name it in the view - into a tuple which we can name in the view. And so now we'll see ... well first thing to see is
it works so there we've got our Index page. But now if you look at the query, here's the good bit, it's only selected the things we wanted: Id, Title and Blurb. Now i'm not going to say that I do that every time, because the actual benefit of performance is pretty tiny, but if you are in a situation where you've got a lot of
columns and it is wasting time pulling them in, then you can get the performance benefit from an anonymous type plus the strong typing benefit of a tuple, and get the best of both worlds.
So that's it - that's the basics of tuples. Next time we're going to be looking at some interesting things you can do with tuples - essentially in how to pull them apart and how to pull classes apart using the concept of a deconstructor. So if you want to see that, do remember to
subscribe. Any questions pop them in the comments and I'll see you next time.