C# Tuples Part 1 - On-the-Fly Types

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Coding Tutorials
Views: 195
Rating: 5 out of 5
Keywords: csharp, tuples, anonymous types, dotnetcore, entityframework
Id: wbZcKd67xSs
Channel Id: undefined
Length: 18min 46sec (1126 seconds)
Published: Fri Aug 14 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.