09 Langauge Features

09 - More Language Features

In this chapter we will look at some other C# features and some loose ends we left along the way, because I didn’t want to bother you with them too early. These aren’t things you definitely need to know from the get go, but they will make you a better programmer. And as soon as you wish to tackle slightly larger projects, come back here.

Properties

C# properties allow you to specify how other scripts can access private variables in your scripts. This comes in handy, if you want a variable to read a value from outside, but don’t want to set it from outside. Reigning this in is done using getter and setters. You define these in curly brackets right after the variable name.

public int myPublicInt;
public int myProperty {get; set; }

If you look at the code, we have a public variable and a public property. These two are the same thing.

public int myProperty {get; }

Now if you try to write to that variable you will run into a compiler error or won’t even be able to compile. So now any Script can read that value, but no script can change the value. You even can’t change it from within the script. But we can manage that:

public int myProperty {get; private set;}

This will allow you to read the script from outside and manipulate it from the inside. This will allow you to protect your variables from accidentally changing them from some other script. So if you need not set these variables from outside, use this method to prevent bugs.

You could also use these to prohibit reading and only allow to write from outside. Any combination is possible.

So what we have seen so far are “auto properties”. They are shorthand code for something that is usually a little longer. In Unity code you will most often see auto properties, but fully fledged properties exist too.

public string myStringProperty
	{
		get { return myStringProperty; }
		set { myStringProperty = value; }
	}

This will result in exactly the same behaviour, it is just more explicit. But using this larger syntax you can also add additional functionality to your getters and setters. You can change the way you return these values. Thus you could for example check if the value you want to check is actually valid inside your context.

public string myStringProperty
	{    
		get { return myStringProperty; }
		set {
			    if (value > 100)
					{
						myStringProperty = 100;
					}
				else
					{   
						myStringProperty = value;
			        }    
			}
	}

So you can add any kind of code into the context of getting and setting values. Obviously you shouldn’t overdo this, but the functionality is there.

Again, you will mostly see me use the auto properties in Unity, which will help protect variables. But if you come across larger chunks of code that start with get or set, you will know how to interpret these.

Delegates and Events

We discussed earlier how Objects can call functions on other objects. To pick a stupid example: I as a “Salesman” Object could call a customer and he could learn from me about all the wonderful things my car offers. I could invoke his “learn” function and the Data I send will be given to him directly.

But what if I have a 100.000 customers? Should I make a call to all of them? While that would be a friendly move, sending out a letter would be fine too, right?

And that’s what Events are all about. If you have information for many, many customers or classes, you just send them out and anyone who subscribed to the event will get notified about it.

So what are delegates and what are Events?

The event is the letter you want to send out. The delegate is an agreement between you and the customer how the event looks like. Say the letterbox. Your customers expect letters in there, but if you suddenly start sending out enormous packages, then the customer can’t receive them, as they are too large.

In code this means the delegate is an agreement between sender and receiver what kind of data is sent with the event.

Thus this system is useful, but it also comes with some advantages for our code. Once we set this up, any class can subscribe to the event, without us ever changing the code in the class that is invoking the event. So this helps us stay flexible in our code.

In larger projects this can also lead to shorter compilation times, which is always a plus!

How to set up events?

Implementing delegates and Events into our projects consists of five essential steps:

  1. Create the agreement about the event a.k.a. the delegate

  2. Create the event based on that delegate

  3. Create the event handler to receive the event

  4. Subscribe to the event

  5. Raise/Call the event

As a first very simple example, we will have our cuboids which is slowly shrinking, but once you press a button, rescale to its initial Size again. Arguably you could solve this without Events, but bear with me.

So we need to create two Scripts. One for the sender of the event and one for the receiver. So let’s just call them Sender and Receiver. So first thing we then need to do is create the agreement or delegate. The delegate belongs into the Sender class.

public class Sender : MonoBehaviour
{
    public delegate void ButtonPressedEventHandler();
    void Start(){...}

The delegate needs to be public so it can be seen by all the other classes. The delegate keyword tells C# that this IS a delegate. Now the void is up to us. Essentially, we define a function, and if it is supposed to return a value, it can. But we don’t need it, so we’ll go with void. Lastly, we define a name for the delegate. Naming follows the same ideas as for methods, but we will use this delegate for handling Events, thus suffixing EventHandler is good practice. This just defines how the functions working with this need to look like.

Next we create the event itself. 

 public event ButtonPressedEventHandler buttonPressed;

It is also public, as it needs to be seen by the subscribers. Followed by the event Keyword. The type is the name of the Delegate we just created and finally we give it a name.

Now it’s switch over to the receiving script. Here we first need to create a function that is called when the event is raised. What’s important here is that this function matches the definition of the delegate we created!

void OnButtonPressed(){
        transform.localScale = Vector3.one * 5;
    }

As you can see, the delegate we created has a return-type of void and takes no arguments. The same goes for our OnButtonPressed function. So that’s easy. On to step four!

Now our receiver needs to subscribe to the event.

 
public GameObject sender;
    void OnEnable()
    {
        sender.GetComponent<Sender>().buttonPressed += OnButtonPressed;
    }

First, we create a reference to the Sender Object as we have done many times before. As we will need to connect these in the Unity Editor!

Then in the OnEnable() method we get the sender component and look for the buttonPressed event. We then add the OnButtonPressed method to the event a.k.a subscribe it to the event. From now on, whenever the Event is raised, the OnButtonPressed() method is called.

So how to raise the event?

We need to do this back in our sender Script. And all we need to do is call the event like any other function we call:

	buttonPressed();

So far, so good. But let’s embed it into our code.

void Update()
    {
        if(Input.GetKeyDown(KeyCode.A)){
            if (buttonPressed != null){
                buttonPressed();
            }
        }
    }

 In Update() we check for someone pressing the “A” key. Then we check if buttonPressed is not Null. If this returns Null it means button pressed has no subscribers and then there is no need to raise the event. Worse! If you don’t check for this, it will throw a Null reference exception at runtime!

But if it’s not null, this will run just fine!

This is outstanding. But let’s clarify more about why this is awesome. Now we can add as many functions to this event as we want! Let’s just create a function to color our sphere randomly:

void OnButtonPressedColor(){
        this.GetComponent<Renderer>().material.color = new Color(Random.Range(0f,1f), Random.Range(0f,1f),Random.Range(0f,1f));
    }

We can then just subscribe this to the event as well:

sender.GetComponent<Sender>().buttonPressed += OnButtonPressedColor;

Now we didn’t have to change anything in the sender class. The sender class doesn’t care or even know about what we did here! You could now easily add new classes that also implement this and would never again need to change the sender class.

Unsubscribing from events

Now one thing that would we consider good practice is unsubscribing once your object get’s disabled or destroyed. That is as simple as subscribing. Just use -= in OnDisable().

 void OnDisable(){
        sender.GetComponent<Sender>().buttonPressed -= OnButtonPressed;
        sender.GetComponent<Sender>().buttonPressed -= OnButtonPressedColor;
    }

Static events

While all of this is fine, it’s also a lot of stuff to take care of. So let’s look into simplifications, now that you know the full version of it. The easiest simplification is to make your event static.

This allows us to not create an object Reference in the receiver first. You can just subscribe to the event calling the class like this:

Sender.buttonPressed += OnButtonPressed;

Actions

We can even go further and reduce the delegate creation and event creation into one line. To do this we first need to import the System namespace. using System;

Then we need to change the way we declare our delegate:

public static Action buttonPressed;

This will now be enough, and we can subscribe directly to this Action. In case you need to add parameters to your events, the syntax changes and you need to provide them in <> braces after the Action keyword. The following code would expect an integer and float as input values.

public static Action<intfloat> buttonPressed;

And thus you need to supply values as arguments when calling the function!  

As you can see Actions can simplify delegates and events a lot. But I wanted to show you the entire process, so you can read code that uses the extended method.