#sure ill make a thread to explain it in

1 messages · Page 1 of 1 (latest)

trail osprey
#

thread here

#

@echo ice

echo ice
#

hi

trail osprey
#

hey hey

#

are you familiar with the term "pass by value"

echo ice
#

no

#

:/

trail osprey
#

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?

echo ice
#

10

trail osprey
#

so thats a common trap

echo ice
#

wait

trail osprey
#

it actually prints 5

echo ice
#

the method doesnt return

trail osprey
#

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?

echo ice
#

hmm

#

so why is it a separate variable

#

wait

#

because its declared in the method

trail osprey
#

yeah exactly

#

parameters are separate variables that have a lifespan of the function

echo ice
#

but they become "the same"

#

because of DoThing(myInt);

trail osprey
#

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

echo ice
#

right

#

and it doesnt get reassigned to 10 because theres no return?

trail osprey
#

so in the following case:

void DoThing(int i)
{
  i = 10;
}

int myInt = 5;
DoThing(myInt);
Console.WriteLine(myInt);
trail osprey
quaint aurora
#

When you're doing DoThing(myInt)

You're basically doing DoThing(5), myInt isn't being changed

trail osprey
echo ice
#

would it work if it was return i = 10;

trail osprey
#

it would not, no

#

forget about the return value altogether here

echo ice
#

okay

trail osprey
#

the main important takeaway is that in a function call, the parameters are their own separate variables

#

does that make sense so far?

echo ice
#

yes

trail osprey
#

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:

echo ice
#

tricky

trail osprey
#
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

echo ice
#

thats strange because i feel like ive never had these problems with methods without ref

trail osprey
#

you probably use more complex types than an int

#

like components or gameobjects or something

echo ice
#

so i understand better. how would i change it without ref

trail osprey
#

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);```
trail osprey
#

in this case, DoThing would properly modify the transform's position

#

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);```
echo ice
#

hence why we need ref

#

or not need

trail osprey
#

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

echo ice
#

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?

trail osprey
#

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

echo ice
#

im going to screen shot this conversation to add to my notes

trail osprey
#

but as long as you understand that value types hold data, whereas reference types hold a reference to data, you'll be fine

echo ice
#

thanks so much 🙂

trail osprey
#

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

trail osprey
#

a reference type's data is a reference

echo ice
#

so a variable within a class?

#

or

#

gameobject.someVariable

#

all ref?

trail osprey
#

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?

echo ice
#

yes

trail osprey
#

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

echo ice
#

im not entirely sure when i'd use it

trail osprey
#

it has its niche uses

echo ice
#

thats what i would do as a noob

trail osprey
#

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

echo ice
#

okay

#

but first

#

i thought you might appreciate that

trail osprey
#

:P

echo ice
#

its hard to work sometiems

trail osprey
#

it's funny cause my cat is named misty

echo ice
#

lol

#

anyways

trail osprey
#

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?

echo ice
#

yes

trail osprey
#

so to recap, out means the exact same thing as ref but it comes with those 2 restrictions

echo ice
#

Understood

#

kind of strange

trail osprey
#

so why use it if it just restricts you? because on the caller end, it allows you to do some cool things

echo ice
#

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

trail osprey
#
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?

echo ice
#

hang on

#

ref connects the internal int i to the external

#

right?

trail osprey
#

yeah

echo ice
#

and because its connected its now reassigned to 10

trail osprey
#

i becomes like an alias to myVar, so updating i to 10 updates myVar to 10

echo ice
#

we need ref on both ends ?

trail osprey
#

yeah

echo ice
#

okay

trail osprey
#

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

echo ice
#

okay...

trail osprey
#

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```
trail osprey
#

confused by that message?

echo ice
#

saving it ^

trail osprey
#

ah perfect

trail osprey
#

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

trail osprey
trail osprey
#

these restrictions

echo ice
#

gotcha

trail osprey
#

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

echo ice
trail osprey
#

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

echo ice
#

so in the first one

#

we are declaring a variable and using it at the same time?

trail osprey
#

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

echo ice
#

runs throught he method i mean

#

neat

trail osprey
#

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
}```
echo ice
#

can we look at a problem i had earlier to learn?

trail osprey
#

this way it returns 2 values:

  1. Whether or not it successfully found a component of that type (the actual return value)
  2. The component if found
echo ice
#

i understand mostly how it works but i dont know how to apply it

trail osprey
echo ice
trail osprey
#

can you copy paste the code?

echo ice
#

so i was trying to figure out how to simplify this so i dont repeat

#

how do i format it like yours

trail osprey
#

```cs
// your code here
```

#

the cs part is what makes it colourful

echo ice
#
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;
        }
    }



trail osprey
#

there you go

echo ice
#

when i asked this, this is when out came up

#

in someone elses example

trail osprey
#

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:

echo ice
#

for learning, how would the out help here?

trail osprey
#
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

echo ice
#

will this thread stay up for awhile?

trail osprey
#

you're allowed to do something even more concise:

echo ice
#

im going to read it all at some point

trail osprey
echo ice
#

sweet

trail osprey
#

just copy a link to it by right clicking one of the messages

trail osprey
# trail osprey you're allowed to do something even more concise:
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 _

echo ice
#

im lost

trail osprey
#

any specific part?

echo ice
#

out _

trail osprey
#

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

echo ice
#

okay but in this case i think i need the ItemGrabber

trail osprey
#

yeah then use the above example

trail osprey
echo ice
#
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

trail osprey
#

so collision.gameObject gives you a reference to the gameobject we're colliding with

#

good on that part?

echo ice
#

yes

trail osprey
#

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

echo ice
#

okay

#

makes sense

trail osprey
#

TryGetComponent will return true if it finds a component of type ItemGrabber on the gameobject

#

it will return false if it doesn't

echo ice
#

wouldnt that also be the case without out

trail osprey
#

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

echo ice
#

not just true

#

so without out. simply true or false

#

with out

trail osprey
#

so you've killed two birds with one stone - checking if it has the component and storing a reference to it

echo ice
#

true or false AND a reference

trail osprey
#

yeah

#

so you've replaced your previous 2 lines with 1

echo ice
#

oh my goodness ive got it

trail osprey
#

woohoo!

echo ice
#

thanks 🙂

trail osprey
#

happy to help :)

echo ice
#

i really really appreciate that

trail osprey
#

my pleasure, i enjoy teaching!

echo ice
#

With TryGetComponent in particular

#

i noticed a change between <> and ()

#

is that because the out does the job of <>, which specifies the type

trail osprey
#

yeah so the <> is a part of another concept called "generics" which is a whole other conversation

echo ice
trail osprey
#

yeah pretty much

echo ice
#

cool

trail osprey
#

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 <>

echo ice
#

one more question

#

is that bad practice

#

or hard on performance

trail osprey
#

it would most likely compile to the same thing

#

but you'd be losing out on the conciseness given by out params

echo ice
#

right