How "out" works in C# and why "in" can make or break your performance

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody i'm nikki in this video i'm going to show you everything you need to know about the in and the out keywords in c sharp only focusing on the parameter modifier aspect of it so not the generic modifier not any covariance or contravariance aspect none of that we're gonna leave that for a different video because we have quite a few things to talk about here and even if you know or you think you know everything you need to know about this trust me you're gonna learn something you don't in this video i'm gonna go really in depth if you like a lot of content and you want to see more make sure you subscribe bringing this notification bell to get highlighted when i upload a new video so let's see what i have here i'm going to start with the out keyword because most of you are used to it but i do have chapters in this video so if you think you know everything trust me you might not but if you think you do just go straight to the in keyword which is more interesting in my opinion so we have just a program.cs here and i'm gonna first start with how you probably have seen the out keyword and you probably have seen it in these try methods usually a method in c sharp will start with try and then it's like try something in our case it's try pause and these methods look usually the same they accept some value let's say just a random number a string and we're going to pass that and then they return the past value if it could be passed but they also return a boolean so the idea is that this method now returns effectively two things it returns whether it could pass this value so whether it's a valid number or integer in this case represented as a string and also the actual value once passed because if i do a console.writeline past value and i print that you see that i'm getting the the value itself as an end so that worked however if that could not be passed it's like this then the could pass method would be false and also the past value would be the default which in this case is zero so this is how you have probably seen it but really both the out and the in keyword are the ref keyword with some flair around it with some either constraints or extra functionality this will cause and this could totally be a value outside of that method i could initialize it as five this could totally be paused like this if i just remove the ends it can be pre-initialized to something in this case it's five then it goes through the top trypass method and then it's returned again now watch what happens and what's why i mentioned it's like the ref keyword with a bit of a flare because usually when value types like an integer are passed into methods as method parameters but effectively passed as copies meaning a new copy of that value will be created in that method so any modification in that method won't affect the previous value however in our case it is affected because out is acting as ref and the reference of that integer value is passed in here maybe that's a bit confusing so let's take a step back and see another example i could have an int here and i won't call this try pause maybe i would call it example out here and i'm going to accept and in value here and i'm going to return nothing let's just say void right so here we have a simple method and if i change that in and out thing with this now i can do whatever i want in this value i can say 20 here i'm getting a warning here and i'm going to explain why but as you can see this value is passed and then printed what do you expect we expect to see 5 because this interval you think this value is passed as a copy so a new int value with that value value 5 is created because this is a value type however we can change this behavior by changing this into a ref and now if i run that the value is 20 because the reference of that value type is passed so i can mutate it that's the whole idea and then the out thing is a step further it's effectively ref but now you have the following constraint because previously i don't have to do anything with that in value right i can run this method pass it as ref i can execute this and as you can see it says five i go through here it all works fine however if i change this to an out parameter and i change this to an out thing here now this does not compile now i have to set the value upon exit every branch of this method has to set that out parameter in our case 10 as copilot suggests and now it works that's basically the out parameter it is building on top the ref keyword allowing us to pass value types or any type as reference types this includes structs and everything and then it requires from the user to set the value of that out parameter now let's take a look at some constraints and for that i need to create a proper class or let's say class out examples here we go i'm going to move this method here and instantiate u var test equals new house example so that's how it would look like and this needs to be obviously public and public and the reason why i did that is because there are some limitations around the the out keyword first you cannot have one of them without and one of them with ref with the same name however you can have one of them without and one of them without out that sounds weird anyway meaning the compiler will decide where this needs to go it detects this is a method now and if you add the out keyword then it detects that this is the one it needs to go just a small little tip now the other big limitation might be that you cannot use this in async methods or any iterator methods that use the yield return or yield break there's also a few things around how positionally they can live especially in extension methods they cannot be the first argument but in my opinion the biggest one is the async thing now it makes sense why that's the case and we have to live with that but that's something you should know now remember that the out keyword was added during a time where tuples wasn't a thing so it appears to me and i don't know this for a fact but it appears to me that especially on the try methods you know the tripods and they try everything um i don't know if the passing by value thing was the biggest driver or whether the ability to return two values from the same method was the one which we now can do with tuples you can now say public you can return both a boolean and a number and you can write your try pause method like this a string value to pause let's see if copilot gets it yeah it does okay cool so now you would be able to do a try pause like this and then have both the boolean and the end returned and if you want to name them you can say could pause and pause value something like that obviously this still has the difference that the value here if you didn't use trypass will not be passed as a reference type but do you need that it depends and we're going to see that later because we're going to run some benchmarks that was everything out related let's take a look at the in keyword now which is newer and it also comes in to fix a very specific problem but before i show you the problem and some benchmarks i'm going to show you in general what it does so let's say we have a method here avoid example in method and this accepts an int or a ref and value and obviously because it's a reference you can set that to whatever value you want meaning if i call this method and i had something like var some value equals whatever and then i pass it down here as a reference then i can say console.writeline and some value and obviously because we're passing by reference the value is now 49 from the previous one now here's what the in keyword is doing i can remove the ref thing here and change ref to end and now the in keyword turns this value here immutable i cannot change it and in fact if this was extract which is again a value type which looks something like this so a bunch of fields and the old-fashioned way of doing properties then even if that was a point and i would say value dot x i would not be able to change that this is the constraint and in fact just to show you that it truly is a ref keyword in steroids effectively i'm gonna go to shoplab dot io which is a website we've talked about time and time again and if i paste that here you can now see on my head that the in keyword here when lowered and if you don't know what lowering is top right corner of the screen it is really a ref keyword with an easy read-only attribute and an in attribute so the compiler knows to add the constraints we just saw the fact that you can't change this and it's now immutable now here's where it becomes more interesting in my opinion let's say we have a method like this that calculates the distance between 3d points in space and currently we don't have an in or an out or anything now as we mentioned before passing structs which are value types like this will create a whole new copy of that struct and especially on larger strikes this can be a significant performance hit because it is no longer a constant thing passing the destruct by value here because the bigger the struck the slower the operation will be however some people opt in to use a ref keyword here which will allow this whole operation to be in constant speed because you're passing the same reference however this of course comes with the drawback that now the reference itself is here and things in here can change it so you might opt in to use the in keyword instead to guarantee now that these are initial don't want to do that if you do that performance will almost always be worse unless whatever you're using here from this tract is marked as read-only let's take a step back i'm going gonna remove that in keyword again and i'm gonna go here and i'm gonna change this a bit currently nothing in here is read only everything can change if i delete that from here here and here now and i delete that as well and i change this to x y and z now these three properties are read only there the original is hidden but as long as they don't have a setter which would not make them read-only then there is a hidden read-only here which as you can see is grayed out because it's just implied because they don't have a setter and you can even have a read only on the struct level now but let's see that in a second so currently we have three read-only properties accessed in this method now if on top of that immutability with the read-only you also add the in keyword because you want them to be passed by reference with all the read-only usability properties then you should only do that when the properties you're accessing here are read only as well or the fields or whatever because if you don't do that then performance will almost always be worse than passing by value because this thing without the read only so let's say this is a set and this is a set and this is a set which will remove the read-only aspect then this will create defensive copies behind the scenes and this is bad how bad well let's take a look at this benchmark i have here so i have three structs here one is an immutable strike and this immutable track has arrayed only on the struct level and then also three read-only properties and all the fields are read-only as well then we have a mutable strike which doesn't have read-only on the strike level and all its properties and fields are fully mutable and then we have a partially mutable one where the struct itself is not read only and the fields are not read only but the properties themselves are and then as you can see here we just have a constructor and that's it and the operations we're doing is we have two tests for every type and it is we add these two properties together and get a new double back and one is using the in keyword and one is not using it and we're gonna see how defensive copying is affecting performance in all these three different types so to do that we're gonna say benchmark runner dot run bench here we go this is marked as release and this will now run and this will take a couple of minutes and we're going to see the results in a second but it should tell us what the performance degradation is where there are defensive copies so results are back and let's see what we have here so the mutable add by value which is if you remember the the fully mutable struct that you used no in keyword here so passing by value and then in keyword here which is passing by reference but with defensive copies twice as slow and this obviously scales the bigger the struct and the more the usage is so you can see how much more expensive this operation can be now the other two are all within a margin of error on the each benchmark execution so both this this and this are basically the same number and both this and this are also the same number because in both cases read-only was used for them and then the in keyword was used as well so you can see that if you are to use the in keyword make sure that the thing that you're using in that method is read-only otherwise performance will be significantly worse i know that this is advanced stuff and at the performance level that we're looking at here it might not be something that concerns you but for those who do please use it because it can really make a difference by the way the in keyword is also subject to all those constraints that the out keyword has no wasting iterations yield break yield return all that so to summarize this is only important if you're really diving into micro optimizations if you don't it's just a good thing to know about now if the size of a read only struct is bigger than in pointer size which depends on the architecture that you're running on then you should use that in keyword for performance reasons but the biggest one is that if you are using the in keyword on a struct just make sure the thing is read only whatever you're using it on is read-only because otherwise you're gonna have defensive copies that will really degrade your performance and may lead to weird behavior nick the editor here so nick the programmer forgot to mention that the in keyword can also be used with reference types and in that case it only is used to express your intent more clearly the fact that you don't want the method to change anything of that object when the method is invoked and that's it really back to nick the programmer well that's all i have for you for this video thank you very much for watching special thanks to my patreons for making videos possible if you want to support me as well you're going to find a link in the description down below leave a like if you like this video subscribe for more like this and ring the bell as well and i'll see you in the next video keep coding
Info
Channel: Nick Chapsas
Views: 12,009
Rating: undefined out of 5
Keywords: Elfocrash, elfo, coding, .netcore, dot net, core, C#, how to code, tutorial, development, software engineering, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, clean code, dotnet, in keyword, in keyword c#, out keyword, out keyword c#, defensive copy c#, defensive copies, how out works in c#, how in works in c#
Id: VCGXubxKL9I
Channel Id: undefined
Length: 16min 44sec (1004 seconds)
Published: Mon Dec 13 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.