#sure ill make a thread to explain it in
1 messages · Page 1 of 1 (latest)
hi
thats okay!
just figuring out where to start from
so consider the following function:
void DoThing(int i)
{
i = 10;
}```
and consider the following code:
int myInt = 5;
DoThing(myInt);
Console.WriteLine(myInt);
What do you expect this to print?
10
so thats a common trap
wait
it actually prints 5
the method doesnt return
in this case the method is void returning, meaning it doesn't return any values, no
but the part im focusing on is the value stored in myInt
when you call DoThing, what actually happens is you're creating a new variable named i that starts with the value passed in
which in this case is the value of myInt, or 5
but it's a completely separate variable
so updating i inside of the method has no bearing on the value of myInt
does that make sense?
yeah exactly
parameters are separate variables that have a lifespan of the function
when you call a function, the "argument" you pass in is just the value that the "parameter" starts with
but they're still 2 separate variables
so in the following case:
void DoThing(int i)
{
i = 10;
}
int myInt = 5;
DoThing(myInt);
Console.WriteLine(myInt);
thats not specifically why, no
When you're doing DoThing(myInt)
You're basically doing DoThing(5), myInt isn't being changed
in this case, i = 10 is updating the parameter's value, which is a separate variable from myInt altogether
would it work if it was return i = 10;
okay
the main important takeaway is that in a function call, the parameters are their own separate variables
does that make sense so far?
yes
if you add the ref keyword to a parameter, the opposite is true - the parameter is the variable passed in
you would need to modify your code to look like this:
tricky
void DoThing(ref int i) // <-- see the ref keyword here
{
i = 10;
}
int myInt = 5;
DoThing(ref myInt); // <-- see the ref keyword here
Console.WriteLine(myInt);```
in this case, i fundamentally is myInt
so this code snippet would print 10
because instead of taking myInt's value when you call the function, you're taking a reference to the variable itself
thats strange because i feel like ive never had these problems with methods without ref
you probably use more complex types than an int
like components or gameobjects or something
so i understand better. how would i change it without ref
in the case of an int, you couldnt
now let's explore the same case but with something like a component instead of an int
void DoThing(Transform transform)
{
transform.position = new Vector3(0, 0, 0);
}
transform.position = new Vector3(1, 1, 1);
DoThing(transform);
Debug.Log(transform.position);```
this is a situation where you wouldn't run into this issue
in this case, DoThing would properly modify the transform's position
that's because unlike an int, which is a value type, a Transform is a reference type (for more info: https://www.tutorialsteacher.com/csharp/csharp-value-type-and-reference-type)
so when you pass a reference type into a method, what you're actualling passing as data is the reference already
but what you couldnt do, is something like this:
void DoThing(Transform transform)
{
transform = new Transform();
}
transform.position = new Vector3(1, 1, 1);
DoThing(transform);
Debug.Log(transform.position);```
on the int, yes
in the above code snippet, that will change the value of transform within that function call, but it won't actually have any effect on the transform you pass into that function as an argument
so the debug log would print (1, 1, 1)
that's because, much like before, transform is a variable local to that method (i apologize for the bad naming since transform is also a property on MonoBehaviour)
when you call DoThing() there, you're setting the value of that parameter to store what was in the passed in argument
but a Transform, as a reference type, holds a reference as the data
so transform is given the reference as its data
so what you're allowed to do is use data that is being referenced, even change that data, but you can't change what that reference points to
does that kind of make sense or should i try again without the confusion of transform?
its okay if none of that made sense, i tried to explain it in unity terms and i feel like it made it worse
that makes sense to me i think. just processing
so we dont need ref for something like, transform or GameObject, or ClassName
because it is a reference type?
basically to sum it up:
- In C#, some types are value types and some are reference types
- The data that value types store is their data
- The data that reference types store is a reference to their data somewhere in memory
at a very basic level, you can tell if a type is a reference type or value type based on whether it's a class or struct
there are some more complicated cases, but that for the most part is true
if you look here, you'll notice that an int is a struct
which makes it a value type
im going to screen shot this conversation to add to my notes
knowing this, it makes for some behaviour that seems inconsistent until you understand the behind the scenes logic
but as long as you understand that value types hold data, whereas reference types hold a reference to data, you'll be fine
thanks so much 🙂
all of that out of the way
you can use the ref keyword on both reference types and value types
in both cases they look something like this:
var myVar;
DoThing(ref myVar);
assuming DoThing looks like:
void DoThing(ref var myParam)
{
}```
this isn't actually valid code, but pretend var is replaced with some other type
in both the cases where var is a value type (such as int) or a reference type (any class, such as a component or gameobject)
the ref keyword used here allows you to modify the data held by myVar within the function call
because myParam essentially becomes an alias to myVar
but remember this
a reference type's data is a reference
so if var is int in the above example, you can overwrite the data (replace it with another int)
but if var is a class in the above example, you can overwrite the reference (replace it to reference another instance)
in that case the gameObject would be the reference
and someVariable is a property of gameObject
does ref make sense at a basic level now?
yes
to sum it up into one sentence, ref parameters essentially make the parameter an alias to the argument passed in, instead of a separate variable
im not entirely sure when i'd use it
it has its niche uses
in most cases, a return value will make most sense
in some cases, it makes more sense to directly modify an argument passed in
but as you see it used, you'll start to learn when to use it where
let's move on to out parameters now
an out parameter is just a fancier ref parameter
:P
its hard to work sometiems
it's funny cause my cat is named misty
so yeah an out parameter is just a fancy ref parameter
but out is used when you don't care about the value passed into the method
so the only thing you care about is passing a value out
this adds some restrictions to how you can use it
consider the following method:
void DoThing(out int i)
{
}```
this is a method that has one parameter: an out int named i
so it's a parameter of type int where you don't care about the value passed in, only the value passed out
the first restriction is that you can't read its value:
void DoThing(out int i)
{
Debug.Log(i); // Compiler error - not allowed
}```
because you've explicitly stated you don't care about the value passed in, it won't let you use it
the second restriction is that in order for the method to compile, you must assign a value to i in all possible cases before the method returns
so the above 2 examples wouldn't compile
void DoThing(out int i)
{
i = 10;
}```
that snippet will compile fine because it meets those 2 restrictions
you're not reading the value, and you're assigning it before you exit
following so far?
yes
so to recap, out means the exact same thing as ref but it comes with those 2 restrictions
so why use it if it just restricts you? because on the caller end, it allows you to do some cool things
ill need to go practice later with some case examples I might find online
because i dont really understand anything until i do it 100 times
void DoThing(ref int i)
{
i = 10;
}
int myVar = 5;
DoThing(ref myVar);
Debug.Log(myVar); // Prints 10
``` In this example, we know that it prints 10 because of how `ref` works
right?
yeah
and because its connected its now reassigned to 10
i becomes like an alias to myVar, so updating i to 10 updates myVar to 10
we need ref on both ends ?
yeah
okay
out would work the exact same way
void DoThing(out int i)
{
i = 10;
}
int myVar = 5;
DoThing(out myVar);
Debug.Log(myVar); // Prints 10```
because it's just a fancier ref
okay...
but what's cool about out over ref is that you can declare your variable inside of the method call:
void DoThing(out int i)
{
i = 10;
}
DoThing(out int myVar);
Debug.Log(myVar); // Prints 10```
confused by that message?
saving it ^
ah perfect
this one im still going over
those 2 restrictions on out params allow for you to declare the variable inside of the method call
the first one that you can't read the value is necessary, because that variable has no value yet
the second one that you must assign it a value before exiting is necessary, because the newly created variable needs to be given a value
but this is all just fancy syntax for this
restrictions?
wait nvm
gotcha
but yeah out is just a flavour of ref that allows you to declare a variable inside of the method call
and comes with those 2 restrictions to allow that to work
still trying to understand this one
but it compiles to the same thing as that example with ref
void DoThing(out int i)
{
i = 10;
}
DoThing(out int myVar);
Debug.Log(myVar); // Prints 10
is the exact same as
void DoThing(out int i)
{
i = 10;
}
int myVar;
DoThing(out myVar);
Debug.Log(myVar); // Prints 10```
which is the exact same as
void DoThing(ref int i)
{
i = 10;
}
int myVar;
DoThing(ref myVar);
Debug.Log(myVar); // Prints 10```
but slightly more concise and clean once you're used to it
not using it yet
because it's an out param, it doesn't actually get used, per se
but it gets assigned to within the function, yes
what's fun about ref and out, is it allows you to "return" multiple values out of a method
whether you pick ref or out generally just depends on if you use the value coming in or not
so take GameObject.TryGetComponent as an example:
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.TryGetComponent(out BoxCollider2D boxCollider))
{
}
}```
TryGetComponent looks something like this:
actually it also includes another concept called generics which will overcomplicate things
so making it a bit simpler, it would look something like this:
bool TryGetComponent(out BoxCollider2D component)
{
// Logic here
}```
can we look at a problem i had earlier to learn?
this way it returns 2 values:
- Whether or not it successfully found a component of that type (the actual return value)
- The component if found
i understand mostly how it works but i dont know how to apply it
sure
can you copy paste the code?
so i was trying to figure out how to simplify this so i dont repeat
how do i format it like yours
private void OnTriggerEnter2D(Collider2D collision)
{
ItemGrabber grabber = collision.GetComponent<ItemGrabber>();
if (grabber)
{
isTouchingCompartment = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
ItemGrabber grabber = collision.GetComponent<ItemGrabber>();
if (grabber)
{
isTouchingCompartment = false;
}
}
there you go
yeah the TryGetComponent wouldn't itself reduce the duplicate code
it was just a side tip
my personal opinion is not to worry about the duplicate code there
and do something like:
for learning, how would the out help here?
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out ItemGrabber grabber))
{
isTouchingCompartment = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out ItemGrabber grabber))
{
isTouchingCompartment = false;
}
}```
and you can even improve it more
because you're not using grabber in this case, you just want to know if it was successful
will this thread stay up for awhile?
you're allowed to do something even more concise:
im going to read it all at some point
it will always show
sweet
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out _))
{
isTouchingCompartment = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out _))
{
isTouchingCompartment = false;
}
}```
if you don't care about the value being given to you by an out param, you can just put out _
im lost
any specific part?
out _
so _ is a global variable of type object
which means you can put any value of any type into it
it exists for the sole purpose of throwing data you don't care about into
okay but in this case i think i need the ItemGrabber
yeah then use the above example
if you care about the component that may exist on the gameobject, use this one
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out ItemGrabber grabber))
{
isTouchingCompartment = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.TryGetComponent(out ItemGrabber grabber))
{
isTouchingCompartment = false;
}
}```
for this one
can we run through whats happening
so collision.gameObject gives you a reference to the gameobject we're colliding with
good on that part?
yes
the type GameObject has a method TryGetComponent
which returns a bool and has an out param to give you the component it may or may not find
TryGetComponent will return true if it finds a component of type ItemGrabber on the gameobject
it will return false if it doesn't
wouldnt that also be the case without out
it would yes, but the out param allows you to use that component that it finds
if it successfully finds a component and returns true, then grabber will contain a reference to the ItemGrabber component
so you've killed two birds with one stone - checking if it has the component and storing a reference to it
true or false AND a reference
oh my goodness ive got it
woohoo!
thanks 🙂
happy to help :)
i really really appreciate that
my pleasure, i enjoy teaching!
With TryGetComponent in particular
i noticed a change between <> and ()
is that because the out does the job of <>, which specifies the type
yeah so the <> is a part of another concept called "generics" which is a whole other conversation
but is this essentially whats happening
yeah pretty much
cool
if it can infer the type, you can omit the <> altogether
so like TryGetComponent(out ItemGrabber grabber) it can infer that the type is ItemGrabber, so you don't need the <>
it would most likely compile to the same thing
but you'd be losing out on the conciseness given by out params
right