I used to work on a C# game development library and this kind of low level performance stuff came up quite a lot. It's great to see C# evolving into a language that makes it easier to write the high performance kind of code without needing to do strange things that makes it harder to use and understand. That said, like anything if you're writing this kind of stuff the only real way to know the right thing to do is to measure and benchmark because there are so many little gotchas.
The 'out' on tryParse is better than tuples for this usecase because you can then put the tryParse in an if-statement and use the parsed result in there or skip the entire branch in only a single line. it is very neat.
Excellent! You forgot one thing: the 'in' keyword is optional to the caller which is not true for 'out' or 'ref'. I'm a fan of providing the 'in' keyword for clarity of intention when it applies. Knew all these things, but I appreciate the review. The original demo of dramatic performance difference I've seen is when using Parallel and BigInteger. It referenced that the origins of the in keyword came from game devs in Unity since they were suffering heavily from copies.
Interesting... So do you any of the places where the _in_ keyword is used in Unity's api? I don't think I've seen it used, but I guess it would make sence for Vector3 and 4x4 Matrix. But I don't know if I've seen any functions for those structs that use in keyword.
@@Andrew90046zero No I haven't gone that deep. As I recall, if you had to transition to a larger coordinate system by using something like BigInteger, constantly making copies as your 'actor' moves around the space is performance crushing. The 'in' keyword negates that completely. That said, more recently, 'readonly structs' should make that a non-issue.
You just reminded me that 'ref' exists. I'm working on a project with struct architecture and was doing all kinds of hoops around them. All I need is ref.
Interesting fact - you can pass a struct as an out parameter without initializing it inside a method if it (a struct) doesn't have any fields inside. And this behaviour is even not described in the documentation.
I really don't understand why reading a property that has a setter creates a defensive copy. I mean the compiler _knows_ that we aren't using the setter, so why would it care that the property has one? That is really weird to me. I really prefer how in c++ you can express your entire intent clearly and nothing happens at runtime behind the scenes. (const references, rvalue references, const-qualified functions, reference-qualified functions, constexpr, consteval, pointers, ...) If something is a const reference, the compiler forbids you from writing to it, it's as easy as that. C++ does not have properties because it doesn't make sense with how assignment works, but if it did, it would just disallow you from invoking the setter by checking that at compile time.
Because the getter is used and since the getter is, at the end of the day, a method that technically can mutate the state of the backing field, the compiler will create defensive copies.
@@nickchapsas But why did it work when you removed only the setter? In that case, the getter is also called... Edit: I just noticed that the backing field is written to be readonly during code lowering when only a getter exists. That makes more sense then
I think the in keyword should be removed in favour of C++ 'const', and thus 'const ref'. It will also allow developers to use readonly values since C# currently can't do this (useful for overridden methods where you want it clear that certain values should remain immutable).
Something that is a little confusing is that, a property is not readonly because it doesn't have a setter. Getters are not automatically readonly (they can mutate state) unless that getter is explicitly marked as readonly. The auto generated getter is marked as readonly under the hood. (ie the "get;" simple getter for properties with implicit backing field). 14:05 But why is, when passing by ref, the fully immutable struct slower than the mutable struct with readonly members? This might look like a few nanoseconds, but it is over 3 times the time.
You're right, i did learn something new. I did not know that mutable byref structs create defensive copies and have more performance overhead than mutable by-value.
I did not even know about the in keyword for method parameters. Interesting stuff, I wonder why it is so little used when it can really signal to the reader that the arguments passed will not be mutated. I see this as a benefit, really. Great vid!
I think that the whole immutability concept in C# is fairly new and it's become more popular after records were added, so we might start seeing it even more in the future
soo much information, have to watch it over and over again. very simple thing I didn't consider, the implications of returning tuples, particularly with nullable types for maximum generalization (with proper null conditionals of course). Does anyone know a clean way of storing tuples with dynamic length and type? Anyone who gets more or less where I'm going with this, please do give me any examples that come to mind.
Could you make a video of what the best way to make events is? As I have been trying to figure out how to like make code in another file run whenever a value changes
@@nickchapsas oh ok I think that might work too because alternatives and other types of events might work better since I just need to be able to detect and execute some code when a thing is true in another class without calling any method in it
I did not know about the defensive copies, that is very interesting. 99% of the time my structs are read-only, but this is still very good to know. For saving stack allocation, is there any benefit from doing it with an int? I would imagine the reference (which I sure must get allocated) would be the size of the int, or am I wrong? For strings, and structs do you think there is an advantage of using ref or in on logging methods?
Wow! I thought all the time that C# needs equivalent of C++ const for argument. Here it is "in" keyword lol The thing I wish for C# to have ASAP is typedef, for a lot of reasons..
No so as I mentioned in the last 30 seconds of the video, fore reference types it doesn't have any performance implication. It is just used if you wanna express intent (telling the reader or consumer that this should be immutable)
@@nickchapsas I don't think that's true. The in and out keywords always cause the parameter to be passed by reference, even if it's a reference type, resulting in what would be Foo*& in C++. A reference type passed as an 'in' parameter will have an additional indirection for every access, just as a value type will.
@@tiagodagostini The JIT could optimize away the double-indirection, yes. In the simple case I examined, it did not, but I don't know if it could under the right circumstances. The C# compiler could not optimize it away though - that would change the public signature of the method, breaking clients built with less clever compilers.
@@carldaniel6510 The signature does not mean much. C++ compilers can and do optimize even keeping the signature, as long as they are able to inline apply the function.
That really seems like a design flaw. If "in" only works well with readonly structs (or readonly properties on those structs), it should be a compiler error to access a non-readonly property altogether. Otherwise it just defeats the purpose of the `in` keyword altogether
My biggest complaint about the in keyword: It should be the default, which means passed parameters should not be modifiable by default, since that is bad practice and can lead to bugs. The compiler should be smart enough to use a copy or a pointer (ref) depending on the struct size. Then all the performance gains of large structs are on by default. The performance degradation when using in seems like a compiler bug. If a param needs to be modified, the ref keyword should be required. Obsiously that ship has probably sailed as it would break code everywhere... Great video Nick.
Yeah I agree with that. The more I dive into functional languages the more I see the mistakes of OOP and languages like C# and Java. Unfortunately it's too late to change that.
@@nickchapsas It's a pity, because you talk so good about each topic you pick. :) And I really can't find answer to 1 basic difference between FP and OOP. In every 2nd FP tutorial, they say "FP is better because there are no null values..." and then they put everything in Option type or something similar, so instead of (x is null) check they test if it's None or Some(x). If it's Some, they take x, pass it to function and put result to Some. So is there really any benefit in not having nulls at all?
@@RsApofis It's not about null itself but rather the idea that the api consumer has to acknowledge the fact that there might be "no value". Languages with null just use the easy way out which can lead to NREs and ultimately cost money. Languages like C# 8+ or Kotlin take the other route and, if used corectly, forces you to deal with nullability. FP isn't about not having null though. There are OOP languages that don't have null.
Hi Nick . Grt video n thanks 👍 Nick could you please explain how nullable data type works . To b precise say for example int? X = null : how compiler treat this statement and memory allocation happens ?? What happens if I add quotation mark next to Int?? Please reply nick .
If the in keyword causes it to enforce not being able to change the object passed in, then why the "defensive copies? Maybe you could make another video explaining what defensive copies are and show how it's lowered. It sounds kind of like the "const" keyword on parameters in C++. a const parameter can't be changed and lets the compiler do some optimizations.
"The in keyword causes it to enforce not being able to change the object passed in." Right. But Point is a mutable type. It can modify its own state if it wants to. You cannot change that behaviour. Calling any method on it could change its state. (and a property is just a method under the hood) The in keyword *does* make it so that you cannot reassign the point1 variable. You told the compiler you don't want the object to change, so the compiler will oblige by making a defensive copy. If the Point type were immutable, then the compiler knows it cannot change and there is no need for a defensive copy.
@@Grimlock1979 ah! That makes sense. C++ allows you to mark methods as const meaning that they guarantee not to change the object. Then if a function has a const parameter, it is not allowed to call methods on that object that are not also const.
So I have to ask then; Is there a way to pass a value type by ref but keep the method from making changes to it without using in, and without the performance penalty on non-readonly fields?
Great video. Do you get defensive copies when using in with get; and init; accessors on properties and for readonly record struct which uses those with a backing field?
Hey nick after hearing what you said about vs in a comment I made a while back I've switched to rider, but there is one problem.. I'm struggling with configuring rider and I'm wondering if you could do a video like "setting rider up for your environment" where you go over settings and features that could help with production.
The moment when people are used to your current videos and then they see this video and for the 1st time they have to increase the playback speed because they're not used to you talking so "slowly" 😂
5:39 Friendly reminder that this should be "what it would look like" or just "how it would look." The construction "how X looks like" isn't used/accepted by most native English speakers. Great info in the video though!
That’s cool, I’ll keep using whichever comes out more natural to me in the moment. This is a software engineering channel not an English lit one. As long as people can understand what I say I don’t mind being wrong grammatically and there isn’t a single person that watched the video and missed anything because of this mistake. Thanks for the tip though!
@@nickchapsas I think your English is really good though. I mean some mistakes always happen, and I personally would definitely want to be corrected if I make a mistake so that I don't look like a fool, but it's not that important. I have to say, this is the nicest way you can be corrected on the internet though, so count your blessings :P
@@barmetler Oh definitely I appreciate people being kind and explaining those things to me but I find them trivial. You see, phrases like this are so hard engraved in my brain that me, actively thinking to not get them wrong will take away from my train of thought at the time so I don't wanna do that. It's something I'd probably never write but when I talk, it's just there.
Hello, a question related to code completion. I see that for example at 3:17 and 4:34, the Rider IDE gives an code completion suggestion by displaying gray text 'in line'. Now I know that feature from Visual Studio where it is called "Whole Line completion". A (new) feature of Intellisense I think. How do you enable this for Rider IDE?
Well, shouldn't precompiler be able to tell if there are operations that could result in modifying any value and then show warning instead or decide whether the defensive copy is needed or not? Especially it should produce warnings with reference types, but it seems like it's not.
It sounds like 'in' is pretty much only useful as an explicit compiler hint for immutable or read-only fields and objects. Otherwise it's just a ref that doesn't let you mutate values within the method scope.
How does this change when using records? From my understanding a record is an immutable reference type so does it play differently? Or because its immutable it works the same as a readonly struct using the in keyword by default (I.e. No keyword)?
records are not immutable. They can still have mutable members. They are classes with a few default implementations behind the scenes for equality checks
Surely if you have to use readonly structs, there's no point in using the in keyword, as it's now impossible to mutate the data anyway, so it's not adding any security...? Seems to me this is the obvious downside of using an architecture that creates OO object code -- do away with the assumption that any method can be called at runtime and the compiler could enforcing "in" constraints at compile time, making run-time performance better...
Great Video! :) But I have one question: Lets say I want to pass a struct with a very huge string inside into a method, is this really going to slow down the code? As far as I understand, the string is stored on the heap and as long as I do not modify it, the copy of the struct will just store a pointer onto the same object on the heap. Essentially this would just copy the pointer but not the string, right?
Yeah it will. You can see it in the benchmark actually. Passing the struct by copying it will take 3.2ns while passing it by reference takes only 0.0154ms which means it is A LOT faster.
@@nickchapsas Thats interesting, but I wonder why it takes so much time. Is it just about copying the pointer or do I got something wrong here? Even though copying the pointer may take a lot of time, it should not scale with the size of the string.
@@AniProGuy It doesn't copy the pointer, it copies the whole object, which, for big objects, is expensive. It's very cheap to pass down a IntPtr.Size reference but quite expensive to create a sizable copy of an object.
@@nickchapsas Yes I understand this, as long as the structs do only store other value types. But I assumed that if I have a refence type member inside the struct (e.g. a string) it would only copy the pointer to the string and not the whole object (since the string is stored on the heap).
@@AniProGuy Yeah that's correct but we have all the other field/properties as well that are also heavy. I don't think I showed an example with a string in a struct actually so I don't have the exact number there.
TLDR; ( No bashing intended though! Great video! ) no modifier - pass a copy of whatever you pass in, noting that if it's a value type, then the copy it's a clone of the value type (effectively a new mem allocation), and if it's a reference type, then the copy is a clone of the reference (a separate new reference but one that points to the same object in memory) ref - pass a copy of the reference held by whatever you pass in; the parameter just points to the same location in memory as the outer variable passed in (even for value types, like int for example) in - akin to a move of the outer reference/var itself into the function, as if it was declared inside, but restricted from modifying it (by assignment, not by methods) out - same as 'in' but the parameter this time MUST be assigned to at least once before exiting the function body
Actually out parameters don't have to be always initialized, try running this code: struct MyStruct1 { } struct MyStruct2 { public int SomeField; } struct MyStruct3 { public MyStruct1 SomeField; } struct MyStruct4 { public MyStruct2 SomeField; } void TestOut1(out MyStruct1 myStruct1) { // This compiles } void TestOut2(out MyStruct2 myStruct2) { // This does not compile } void TestOut3(out MyStruct3 myStruct3) { // This compiles } void TestOut4(out MyStruct4 myStruct4) { // This does not compile }
In Java and C# if you pass a variable to a method it is passed by value. That means, if you change fields of the passed variable it affects the variable at the "outside" but if you assign a different instance (a different value) to the passed variable it gets disconnect from the one you passed in the method. With out and ref you work around this mechanic.
@@pfili9306 Oh it's funny every single time for me. Keep in mind that in just the last 30 days the channel saw 50k new unique viewers. It would be a shame to not introduce them to true random numbers.
@@dorlugasigal Oh thank you! I spend quite a lot of time tryint o tweak it so I was worried that there was a sound quality issue. Yeah my setup is Shure SM7B, GoXRL and CL1 Cloudlifter. In the software I've lowered the lows and the highs a bit and I've added a noise gate and a compressor. In think that's all
Why they didn't just have it as 'const' or 'readonly' rather than 'in' which to me would improve the readability by a significant amount, is beyond me... 'in' on a parameter from a readability standpoint makes zero sense.
I have no idea why they make "in" to be able to be copied implicitly and hiddenly? As if the design decision simply allows its misuse. This is so weird.
As Nick pointed out in another comment, the reason for the defensive copies is because the getter can technically mutate the value (even if no one in their right mind should normally do that). If you remove the setter then the property becomes implicitly readonly so it's guaranteed to be impossible for the value to change.
@@clonkex Yea. After all it always looks like you just ~can not~ have a concise poetry without a very complicated actual meaning. The world just doesn't seem fair.
Are you talking about int.TryParse? I guess it could be bug-prone if you ignore the returned boolean. However, your ide will warn you about unused return values, so I don't see the issue. int.Parse is MUCH slower than int.TryParse in case of an exception. Let me extend you vocabulary with the following way int.TryParse can be used in one expression: int.TryParse(str, out var result) ? $"Success: {result}" : "Failure" When failure happens, the expression case will take orders of magnitude more time and memory.
@@barmetler I mean in general. Its ultimately a way for multiple "return" values, typically for errors, much like exceptions in java, tuples in Go and separate callback functions in RX. Then again, I don't even like mutable objects so I'm certainly not down with deviant stuff like mutable references!
@@adambickford8720 I personally prefer tuples over references, but exceptions really are just intended for situations where the program does something _unexpected._ Invalid user input, i.e., is not unexpected, so the program is not in a faulty state after that. Exceptions are there to protect against bugs, like calling a library function with invalid arguments. That's why exceptions are allowed to be so expensive.
@@barmetler I'm talking the general mechanism as a language feature, not the ways its (mis)used in practice. A function generally returns a certain 'shape' (a User, a Func, etc). It's really uncommon to have a contract return radically different shapes (in fact, we try hard to hide those through interfaces and OO, etc), except for errors. A function essentially has a return 'shape' that is the union of all the possible "exits". All of those mechanisms are different ways to express that divergence in the return shape. Each have their pros and cons but I personally don't like the idea that some code, far away, could hold a reference and (asynchronously to my thread) change a value. Looking at the docs it seems they are steering people towards tuples.
@@nickchapsas the problem is, you cannot know if the user using your API or yourself from the future are not going to change the read-only struct to a mutable struct and shoot yourself in the foot (in performance critical scenarios). Ref is easier to remember and the caller could make the defensive copy by himself if he wants to preserve the value.
you bring a valid point. using “in” is an optimisation, it can potentially run faster than “ref” in multi-threaded scenarios since it gives hint to your CPU that “this block of memory is readonly, you dont have to sync this across multiple cores”
@@Miggleness That's not true at all. In is exactly the same as out and ref under the hood, and C# never does thread synchronization unless you explicitly tell it to. That's also why you cannot overload a method simply by changing in, out, or ref.
@@protox4 i wasnt talking about thread sync in c# but rather in the memory sync on NUMA cpu architecture. anyhow, looks like you’re right about ref vs in. they should have identical performance with readonly structs
Great video. Do you get defensive copies when using in with get; and init; accessors on properties and for readonly record struct which uses those with a backing field?
Constructive programming note: when replacing the random number 69 with a string, you should always use the random string "Nice"
I'll open a PR for Microsoft to change. Thanks for pointing up.
that would break code
@@cscscscss Why, exactly?
🤣😅
I used to work on a C# game development library and this kind of low level performance stuff came up quite a lot. It's great to see C# evolving into a language that makes it easier to write the high performance kind of code without needing to do strange things that makes it harder to use and understand. That said, like anything if you're writing this kind of stuff the only real way to know the right thing to do is to measure and benchmark because there are so many little gotchas.
After a few years in game dev mainly graphics and optimization there is only one aphorism i swear ny, a good test is worth a thousand expert opinions!
And we can hardly use any of the new features BECAUSE UNITY WONT UPDATE THE COMPILER
The 'out' on tryParse is better than tuples for this usecase because you can then put the tryParse in an if-statement and use the parsed result in there or skip the entire branch in only a single line. it is very neat.
You can pattern match on tuples.
if (await SomeMethod() is (succes: true, result: {} value)) DoStuff(value);
@@anderskehlet4196 Interesting! I did not know that.
Awesome video for knowing ins and outs of c#
i see what you did here
Nice.
Underrated comment
Excellent! You forgot one thing: the 'in' keyword is optional to the caller which is not true for 'out' or 'ref'.
I'm a fan of providing the 'in' keyword for clarity of intention when it applies. Knew all these things, but I appreciate the review.
The original demo of dramatic performance difference I've seen is when using Parallel and BigInteger. It referenced that the origins of the in keyword came from game devs in Unity since they were suffering heavily from copies.
Interesting... So do you any of the places where the _in_ keyword is used in Unity's api?
I don't think I've seen it used, but I guess it would make sence for Vector3 and 4x4 Matrix. But I don't know if I've seen any functions for those structs that use in keyword.
@@Andrew90046zero No I haven't gone that deep. As I recall, if you had to transition to a larger coordinate system by using something like BigInteger, constantly making copies as your 'actor' moves around the space is performance crushing. The 'in' keyword negates that completely. That said, more recently, 'readonly structs' should make that a non-issue.
You just reminded me that 'ref' exists. I'm working on a project with struct architecture and was doing all kinds of hoops around them. All I need is ref.
English language made me chuckle hard on the "One of them with out, and one of them without.......out" :) lovely comedic insert
You missed the perfect opportunity for a video name here...
"The ins and outs of the in and out keywords"
YOU ARE HIRED
Interesting fact - you can pass a struct as an out parameter without initializing it inside a method if it (a struct) doesn't have any fields inside. And this behaviour is even not described in the documentation.
Yeah someone commented this one and it blew my mind. I didn't know about that
AFAIK a struct is automatically initialized. So this actually makes sense; If there are no fields to set then there's simply nothing to be done.
I really don't understand why reading a property that has a setter creates a defensive copy. I mean the compiler _knows_ that we aren't using the setter, so why would it care that the property has one? That is really weird to me.
I really prefer how in c++ you can express your entire intent clearly and nothing happens at runtime behind the scenes. (const references, rvalue references, const-qualified functions, reference-qualified functions, constexpr, consteval, pointers, ...)
If something is a const reference, the compiler forbids you from writing to it, it's as easy as that. C++ does not have properties because it doesn't make sense with how assignment works, but if it did, it would just disallow you from invoking the setter by checking that at compile time.
Because the getter is used and since the getter is, at the end of the day, a method that technically can mutate the state of the backing field, the compiler will create defensive copies.
@@nickchapsas But why did it work when you removed only the setter? In that case, the getter is also called...
Edit: I just noticed that the backing field is written to be readonly during code lowering when only a getter exists. That makes more sense then
I think the in keyword should be removed in favour of C++ 'const', and thus 'const ref'.
It will also allow developers to use readonly values since C# currently can't do this (useful for overridden methods where you want it clear that certain values should remain immutable).
Something that is a little confusing is that, a property is not readonly because it doesn't have a setter. Getters are not automatically readonly (they can mutate state) unless that getter is explicitly marked as readonly. The auto generated getter is marked as readonly under the hood. (ie the "get;" simple getter for properties with implicit backing field).
14:05 But why is, when passing by ref, the fully immutable struct slower than the mutable struct with readonly members? This might look like a few nanoseconds, but it is over 3 times the time.
i haven’t seen this mentioned, you can run into defensive copies too with “ref”s if what you’re passing as ref is declared as read only
You're right, i did learn something new. I did not know that mutable byref structs create defensive copies and have more performance overhead than mutable by-value.
Hi Nick, Great video.
I'll really appreciate a video on Covariance and Contravariance
I did not even know about the in keyword for method parameters. Interesting stuff, I wonder why it is so little used when it can really signal to the reader that the arguments passed will not be mutated. I see this as a benefit, really. Great vid!
I think that the whole immutability concept in C# is fairly new and it's become more popular after records were added, so we might start seeing it even more in the future
soo much information, have to watch it over and over again. very simple thing I didn't consider, the implications of returning tuples, particularly with nullable types for maximum generalization (with proper null conditionals of course).
Does anyone know a clean way of storing tuples with dynamic length and type? Anyone who gets more or less where I'm going with this, please do give me any examples that come to mind.
How to set random values:
Noobs: var x = 1;
Nerds: var x = 3.14159;
Nick: var x = 69;
I always just go for 42
Could you make a video of what the best way to make events is? As I have been trying to figure out how to like make code in another file run whenever a value changes
Do you mean C# events?
@@nickchapsas yes I mean C# events
@@patfre I don't think I'm planning on creating a video about events but I am going to be creating a video about why you don't need traditional events
@@nickchapsas oh ok I think that might work too because alternatives and other types of events might work better since I just need to be able to detect and execute some code when a thing is true in another class without calling any method in it
Maybe you can try to look into Reactive programing? There is the Reactive Extensions library for this in .NET
I did not know about the defensive copies, that is very interesting.
99% of the time my structs are read-only, but this is still very good to know.
For saving stack allocation, is there any benefit from doing it with an int?
I would imagine the reference (which I sure must get allocated) would be the size of the int, or am I wrong?
For strings, and structs do you think there is an advantage of using ref or in on logging methods?
Wow! I thought all the time that C# needs equivalent of C++ const for argument. Here it is "in" keyword lol
The thing I wish for C# to have ASAP is typedef, for a lot of reasons..
I recommend Konrad Kokosa videos and readings about performance. He knows the subject :)
Konrad is great. I’ve read his book, it’s amazing, even though half of it went over my head
I didn't know about struct size vs intptr size and performance. Can't seem to find that in a quick google.
Great video. thanks. So does it affect performance for the reference types as well?
No so as I mentioned in the last 30 seconds of the video, fore reference types it doesn't have any performance implication. It is just used if you wanna express intent (telling the reader or consumer that this should be immutable)
@@nickchapsas I don't think that's true. The in and out keywords always cause the parameter to be passed by reference, even if it's a reference type, resulting in what would be Foo*& in C++. A reference type passed as an 'in' parameter will have an additional indirection for every access, just as a value type will.
@@carldaniel6510 But a compiler can optimize it if he knows it is "immutable", but I do not know how much C# compilers see outside immediate scope.
@@tiagodagostini The JIT could optimize away the double-indirection, yes. In the simple case I examined, it did not, but I don't know if it could under the right circumstances. The C# compiler could not optimize it away though - that would change the public signature of the method, breaking clients built with less clever compilers.
@@carldaniel6510 The signature does not mean much. C++ compilers can and do optimize even keeping the signature, as long as they are able to inline apply the function.
That really seems like a design flaw. If "in" only works well with readonly structs (or readonly properties on those structs), it should be a compiler error to access a non-readonly property altogether.
Otherwise it just defeats the purpose of the `in` keyword altogether
It's the other way around. in is coming in to fix the flaw that was there with ref readonly
My biggest complaint about the in keyword: It should be the default, which means passed parameters should not be modifiable by default, since that is bad practice and can lead to bugs. The compiler should be smart enough to use a copy or a pointer (ref) depending on the struct size. Then all the performance gains of large structs are on by default. The performance degradation when using in seems like a compiler bug. If a param needs to be modified, the ref keyword should be required. Obsiously that ship has probably sailed as it would break code everywhere... Great video Nick.
Yeah I agree with that. The more I dive into functional languages the more I see the mistakes of OOP and languages like C# and Java. Unfortunately it's too late to change that.
@@nickchapsas Does it mean you'll will cover also F# or some other functional language in the future?
@@RsApofis I don't think so. I might make something along the lines of "A C# developer's guide to F#" or something but probably not more than that.
@@nickchapsas It's a pity, because you talk so good about each topic you pick. :) And I really can't find answer to 1 basic difference between FP and OOP. In every 2nd FP tutorial, they say "FP is better because there are no null values..." and then they put everything in Option type or something similar, so instead of (x is null) check they test if it's None or Some(x). If it's Some, they take x, pass it to function and put result to Some. So is there really any benefit in not having nulls at all?
@@RsApofis It's not about null itself but rather the idea that the api consumer has to acknowledge the fact that there might be "no value". Languages with null just use the easy way out which can lead to NREs and ultimately cost money. Languages like C# 8+ or Kotlin take the other route and, if used corectly, forces you to deal with nullability. FP isn't about not having null though. There are OOP languages that don't have null.
Translatign to C++, in is const& in parameter.
Hi Nick . Grt video n thanks 👍 Nick could you please explain how nullable data type works . To b precise say for example int? X = null : how compiler treat this statement and memory allocation happens ?? What happens if I add quotation mark next to Int?? Please reply nick .
Legend says he's still waiting to this day
Strangely, today I learned "out" is "ref" in IL and several hours later saw your video
Great video; thank you!
What about ref struct and readonly ref struct? Let me know if you are gonna benchmark that. Love your content. Bye!
If the in keyword causes it to enforce not being able to change the object passed in, then why the "defensive copies? Maybe you could make another video explaining what defensive copies are and show how it's lowered.
It sounds kind of like the "const" keyword on parameters in C++. a const parameter can't be changed and lets the compiler do some optimizations.
"The in keyword causes it to enforce not being able to change the object passed in."
Right. But Point is a mutable type. It can modify its own state if it wants to. You cannot change that behaviour. Calling any method on it could change its state. (and a property is just a method under the hood)
The in keyword *does* make it so that you cannot reassign the point1 variable.
You told the compiler you don't want the object to change, so the compiler will oblige by making a defensive copy. If the Point type were immutable, then the compiler knows it cannot change and there is no need for a defensive copy.
@@Grimlock1979 ah! That makes sense. C++ allows you to mark methods as const meaning that they guarantee not to change the object. Then if a function has a const parameter, it is not allowed to call methods on that object that are not also const.
You failed to link your lowering video. "Click up here" Thanks for the content.
So I have to ask then; Is there a way to pass a value type by ref but keep the method from making changes to it without using in, and without the performance penalty on non-readonly fields?
Great video. Do you get defensive copies when using in with get; and init; accessors on properties and for readonly record struct which uses those with a backing field?
@Nick Chapsas do you use "copilot" or something else?
I use copilot
Sound and clear!
12:00 why does this happen? Is it a pointer for either in or ref?
Hey nick after hearing what you said about vs in a comment I made a while back I've switched to rider, but there is one problem.. I'm struggling with configuring rider and I'm wondering if you could do a video like "setting rider up for your environment" where you go over settings and features that could help with production.
That’s actually not a bad idea for a video. I’ll add it in the backlog
@@nickchapsas WOOP WOOP LETS GOO
Can you do a video about covariance and contravariance please?
The moment when people are used to your current videos and then they see this video and for the 1st time they have to increase the playback speed because they're not used to you talking so "slowly" 😂
I like Nick the editor. Swell fella.
5:39 Friendly reminder that this should be "what it would look like" or just "how it would look." The construction "how X looks like" isn't used/accepted by most native English speakers. Great info in the video though!
That’s cool, I’ll keep using whichever comes out more natural to me in the moment. This is a software engineering channel not an English lit one. As long as people can understand what I say I don’t mind being wrong grammatically and there isn’t a single person that watched the video and missed anything because of this mistake. Thanks for the tip though!
@@nickchapsas I think your English is really good though. I mean some mistakes always happen, and I personally would definitely want to be corrected if I make a mistake so that I don't look like a fool, but it's not that important.
I have to say, this is the nicest way you can be corrected on the internet though, so count your blessings :P
@@barmetler Oh definitely I appreciate people being kind and explaining those things to me but I find them trivial. You see, phrases like this are so hard engraved in my brain that me, actively thinking to not get them wrong will take away from my train of thought at the time so I don't wanna do that. It's something I'd probably never write but when I talk, it's just there.
@@nickchapsas Yeah makes sense
Hello, a question related to code completion.
I see that for example at 3:17 and 4:34, the Rider IDE gives an code completion suggestion by displaying gray text 'in line'. Now I know that feature from Visual Studio where it is called "Whole Line completion". A (new) feature of Intellisense I think. How do you enable this for Rider IDE?
I think that might have been GitHub Copilot
I'm waiting for the var & co-var :)
Well, shouldn't precompiler be able to tell if there are operations that could result in modifying any value and then show warning instead or decide whether the defensive copy is needed or not? Especially it should produce warnings with reference types, but it seems like it's not.
is this still valid in NET 7? I've ran my own benchmark with the same thing as yours and all got the same speed.
It sounds like 'in' is pretty much only useful as an explicit compiler hint for immutable or read-only fields and objects. Otherwise it's just a ref that doesn't let you mutate values within the method scope.
What autocode extension are you using? Want to install the same))
You promised a link @ 9:40
Thanks for video! Can You say what estension give you that gray suggestion at 3:17 ?
And how do you write code outside the class?
Hey, the suggestions is a new feature in Visual Studio 2022. And "code outside class" is a top-level statements feature introduced in .Net6/C#10
@@ernest1520 thanks, but its not VS 2022, its Rider IDE
Ah yes fair point! :) Knowing Jetbrains their suggestions are probably even more powerful.
That would be GitHub Copilot, look it up, it's amazing!
@@ernest1520 Top-level statements were introduced in C# 9 and the suggestions are GitHub Copilot
i kinda want tryparse to return null if its not parseable and an int if it is
there is an int? type
How does this change when using records? From my understanding a record is an immutable reference type so does it play differently? Or because its immutable it works the same as a readonly struct using the in keyword by default (I.e. No keyword)?
records are not immutable. They can still have mutable members. They are classes with a few default implementations behind the scenes for equality checks
@@nickchapsas Thanks for the clarification. Looks like I should do some more diving into the documentation on records.
@@DryBones111 Lucky you, I hav a video about records coming out next Thursday
Surely if you have to use readonly structs, there's no point in using the in keyword, as it's now impossible to mutate the data anyway, so it's not adding any security...?
Seems to me this is the obvious downside of using an architecture that creates OO object code -- do away with the assumption that any method can be called at runtime and the compiler could enforcing "in" constraints at compile time, making run-time performance better...
What about my structs being passed inside of a class
What version of rider is this? It looks different
does "in" also create defensive copy for mutable class?
Only if the members you're accessing aren't readonly
Great Video! :)
But I have one question:
Lets say I want to pass a struct with a very huge string inside into a method, is this really going to slow down the code? As far as I understand, the string is stored on the heap and as long as I do not modify it, the copy of the struct will just store a pointer onto the same object on the heap. Essentially this would just copy the pointer but not the string, right?
Yeah it will. You can see it in the benchmark actually. Passing the struct by copying it will take 3.2ns while passing it by reference takes only 0.0154ms which means it is A LOT faster.
@@nickchapsas
Thats interesting, but I wonder why it takes so much time. Is it just about copying the pointer or do I got something wrong here?
Even though copying the pointer may take a lot of time, it should not scale with the size of the string.
@@AniProGuy It doesn't copy the pointer, it copies the whole object, which, for big objects, is expensive. It's very cheap to pass down a IntPtr.Size reference but quite expensive to create a sizable copy of an object.
@@nickchapsas
Yes I understand this, as long as the structs do only store other value types. But I assumed that if I have a refence type member inside the struct (e.g. a string) it would only copy the pointer to the string and not the whole object (since the string is stored on the heap).
@@AniProGuy Yeah that's correct but we have all the other field/properties as well that are also heavy. I don't think I showed an example with a string in a struct actually so I don't have the exact number there.
In is basically the C const pointer
TLDR; ( No bashing intended though! Great video! )
no modifier - pass a copy of whatever you pass in, noting that if it's a value type, then the copy it's a clone of the value type (effectively a new mem allocation), and if it's a reference type, then the copy is a clone of the reference (a separate new reference but one that points to the same object in memory)
ref - pass a copy of the reference held by whatever you pass in; the parameter just points to the same location in memory as the outer variable passed in (even for value types, like int for example)
in - akin to a move of the outer reference/var itself into the function, as if it was declared inside, but restricted from modifying it (by assignment, not by methods)
out - same as 'in' but the parameter this time MUST be assigned to at least once before exiting the function body
Impressive 👍
Turtles mating :
In
out
in
out
what code completion tool is he using?
Rider IDE
The IDE is Rider and he's got Github's Copilot as well
public double X{ readonly get => _x; set => _x = value; } error CS0106: The modifier 'readonly' is not valid for this item
Are you using a struct?
Вопрос: нахуй тебе ридонли в геттере?
Maybe your using an older .Net version?
@@Шпрот-в7и ради 3 нс видимо :D или сколько у него там вышло
@@10199able Микрооптимизации головного мозга...
Actually out parameters don't have to be always initialized, try running this code:
struct MyStruct1
{
}
struct MyStruct2
{
public int SomeField;
}
struct MyStruct3
{
public MyStruct1 SomeField;
}
struct MyStruct4
{
public MyStruct2 SomeField;
}
void TestOut1(out MyStruct1 myStruct1)
{
// This compiles
}
void TestOut2(out MyStruct2 myStruct2)
{
// This does not compile
}
void TestOut3(out MyStruct3 myStruct3)
{
// This compiles
}
void TestOut4(out MyStruct4 myStruct4)
{
// This does not compile
}
Oh interesting, I wasn't aware of this one
@@nickchapsas I have a link to article about this, but can't send links here, could you give me a permission to send a link?
That's why we can do out var!
I don't set the rules, RUclips just randomly flags them. Could you please paste the name of the blog so I can find it in google? I wanna read it.
@@nickchapsas "Should we initialize an out parameter before a method returns?" by pvs-studio
In Java and C# if you pass a variable to a method it is passed by value. That means, if you change fields of the passed variable it affects the variable at the "outside" but if you assign a different instance (a different value) to the passed variable it gets disconnect from the one you passed in the method. With out and ref you work around this mechanic.
nice!
I don't even use most of this stuff, maybe because half the time I code in VB 😂
When the first number Nick picks is "69" to convert...
Nice. Using just a random number to explain 😂😂
I wish he would stop already. It was funny for a first few times but at this point it's a little over the top to have it in every video.
@@pfili9306 Personally I still find it funny.
@@pfili9306 Oh it's funny every single time for me. Keep in mind that in just the last 30 days the channel saw 50k new unique viewers. It would be a shame to not introduce them to true random numbers.
@@pfili9306 There are over 420 other videos that explain the in keyword. Go to them.
@@nickchapsas YES. They need to be educated. Its your responsibility. 😂😂😂😂
The thing surprised me the most is your ability to run program without program entry point. Where's static Main method?
It's implied. That was added a year ago in C# 9
Hello everybody I'm gibberish
I should rename the channel at this point
The defensive copies the compiler generates really violates the principle of least surprise in my opinion.
not a relevant question, what mic setup do you use?
Shure SM7B
@@nickchapsas do you use cloudlifter or some other gain boost pre amp?
@@dorlugasigal TC Helicon Go XLR so I don't need the cloudlifter, but I had one anyway so I'm using it too. Do you mean the sound is good or bad btw?
@@nickchapsas its awesome, better than your old videos with the silver mic
@@dorlugasigal Oh thank you! I spend quite a lot of time tryint o tweak it so I was worried that there was a sound quality issue. Yeah my setup is Shure SM7B, GoXRL and CL1 Cloudlifter. In the software I've lowered the lows and the highs a bit and I've added a noise gate and a compressor. In think that's all
Kya baat
U wouldn't call 420 "whatever"... or maybe I would :-D
That's what she said 🤨😁
👍🏽
Can you also do Method(ref int newInt) instead of using an existing int (like you can with out)?
Eu sei que tem um brasileiro lendo isso, então eu conto ou vc conta?🤣
Why they didn't just have it as 'const' or 'readonly' rather than 'in' which to me would improve the readability by a significant amount, is beyond me... 'in' on a parameter from a readability standpoint makes zero sense.
I have no idea why they make "in" to be able to be copied implicitly and hiddenly? As if the design decision simply allows its misuse. This is so weird.
As Nick pointed out in another comment, the reason for the defensive copies is because the getter can technically mutate the value (even if no one in their right mind should normally do that). If you remove the setter then the property becomes implicitly readonly so it's guaranteed to be impossible for the value to change.
@@clonkex Yea. After all it always looks like you just ~can not~ have a concise poetry without a very complicated actual meaning. The world just doesn't seem fair.
Not sure this is better than exceptions honestly. It just opens the door for too many tricky bugs, the cure is worse than the disease.
Are you talking about int.TryParse?
I guess it could be bug-prone if you ignore the returned boolean. However, your ide will warn you about unused return values, so I don't see the issue.
int.Parse is MUCH slower than int.TryParse in case of an exception.
Let me extend you vocabulary with the following way int.TryParse can be used in one expression:
int.TryParse(str, out var result) ? $"Success: {result}" : "Failure"
When failure happens, the expression case will take orders of magnitude more time and memory.
@@barmetler I mean in general.
Its ultimately a way for multiple "return" values, typically for errors, much like exceptions in java, tuples in Go and separate callback functions in RX.
Then again, I don't even like mutable objects so I'm certainly not down with deviant stuff like mutable references!
@@adambickford8720 I personally prefer tuples over references, but exceptions really are just intended for situations where the program does something _unexpected._ Invalid user input, i.e., is not unexpected, so the program is not in a faulty state after that. Exceptions are there to protect against bugs, like calling a library function with invalid arguments. That's why exceptions are allowed to be so expensive.
@@barmetler I'm talking the general mechanism as a language feature, not the ways its (mis)used in practice.
A function generally returns a certain 'shape' (a User, a Func, etc). It's really uncommon to have a contract return radically different shapes (in fact, we try hard to hide those through interfaces and OO, etc), except for errors.
A function essentially has a return 'shape' that is the union of all the possible "exits". All of those mechanisms are different ways to express that divergence in the return shape.
Each have their pros and cons but I personally don't like the idea that some code, far away, could hold a reference and (asynchronously to my thread) change a value.
Looking at the docs it seems they are steering people towards tuples.
TLDW: use ref, never use in
Edit: wells, it's not what the video says but my personal recommendation in case of doubt
That is not what the video is about and it is not the conclusion of the video either.
@@nickchapsas the problem is, you cannot know if the user using your API or yourself from the future are not going to change the read-only struct to a mutable struct and shoot yourself in the foot (in performance critical scenarios). Ref is easier to remember and the caller could make the defensive copy by himself if he wants to preserve the value.
you bring a valid point. using “in” is an optimisation, it can potentially run faster than “ref” in multi-threaded scenarios since it gives hint to your CPU that “this block of memory is readonly, you dont have to sync this across multiple cores”
@@Miggleness That's not true at all. In is exactly the same as out and ref under the hood, and C# never does thread synchronization unless you explicitly tell it to. That's also why you cannot overload a method simply by changing in, out, or ref.
@@protox4 i wasnt talking about thread sync in c# but rather in the memory sync on NUMA cpu architecture. anyhow, looks like you’re right about ref vs in. they should have identical performance with readonly structs
"in this case 10, AS THE COMPILER SUGGESTS" xD
he actually said "as copilot suggests", since he's using github copilot
pointers
Don't ever write code with out or ref. It makes the code very hard to track and leads to spaghetti
You might have very valid reasons to use ref so saying not use it because you find it hard to follow isn’t good advice
Out is sometimes necessary to avoid allocations
Great video. Do you get defensive copies when using in with get; and init; accessors on properties and for readonly record struct which uses those with a backing field?