Ever want to set an int or Vector3 to null?
What Are Nullable Types?
Nullable types are roughly a way to set a value type to null. Common value types are: int, double, float, bool, structs, enums, and in Unity: Vector2 and Vector3. You probably already know that if you try something like the following,you will get some error messages.
int i = null; //or in Unity Vector3 v = null;
These types can’t normally be set to null. However C# has a cool trick that allows you to assign null to any of these types! You can create a Nullable Structure that has an underlying type. This structure can be any value of the underlying type, OR null.
- A nullable structure with an underlying int could be any value from -2,147,483,648 to 2,147,483,647, or null.
- A nullable structure with an underlying bool could be true, false, or null
- A nullable structure with an underlying Vector3 could be any Vector3, or null
Fortunately C# makes it really easy to create a nullable type by adding a ‘?’ after the variable type in the declaration.
int i = null; //Not nullable, syntax error int? j = null; //Nullable! bool b = null; //Not nullable, syntax error bool? c = null;//Nullable! Vector3 v = null;//Not nullable, syntax error Vector3? w = null;//Nullable!
How could we then check if the above variables are null, or they have values? We can just use regular if statements!
if(j == null) //Do something if(c == null || c == true) //Do something if(w != null && w == Vector3.up) //Do something
Hopefully this introduces what nullable types are, even if you’re not sure when you would ever use them. Don’t worry, we’ll get there. If you have any more questions about Nullable types, you could try the official documentation. Otherwise, let’s move on to how we could use these in Unity.
A Quick Example
A while back, I was working on the AI of small game. I wanted to make a method that would return the location of the nearest NPC, so enemies could start moving towards it to attack it.
public Vector3 findNearestNPCPosition(){ //Let's pretend there is only one NPC for now GameObject npc = GameObject.FindGameObjectWithTag("NPC"); return npc.transform.position; }
But here’s a problem: What would happen if there are NO npcs? We would get a null reference exception on line 4, when we try to access transform! Well, what should our method do if there is no NPC? Let’s look at a couple options:
- Return Vector3.zero. (Aka return (0,0,0)).
- This isn’t a great idea, because what if an NPC actually was at (0,0,0)? Isn’t that still a position? That would make the return rather ambiguous.
- Use another method to make sure there is an NPC first, and call that method before findNearestNPCPosition every time.
- This is a solution, but it seems cumbersome. Also if we ever forget to check first, we will still get an error.
- Rewrite the method to return a nullable Vector3.
- This seems like a more elegant solution. We simply need to check if the method returned null or an actual vector.
Let’s try changing the method to return a nullable Vector3, rather than a regular old Vector3.
/* * Changed method, using nullable types */ //Notice the question mark after the return type public Vector3? findNearestNPCPosition(){ //Let's pretend there is only one NPC for now GameObject npc = GameObject.FindGameObjectWithTag("NPC"); //Check if an npc exists or not if(npc == null){ //This would be a syntax error before! return null; } else { return npc.transform.position; } } /* * Let's see how we could call this method in another method */ public void AI(){ Vector3? npcPosition = findNearestNPCPosition(); if(npcPosition == null){ //Do nothing, or find something else to do }else{ //Let's move a bit towards the npc float distance = 1; //Max distance moved Vector3 newPosition = Vector3.MoveTowards(transform.position,npcPosition,distance); transform.position = newPosition; } }
A Final Word Of Warning
Don’t overuse Nullable types! Nullable types are a great tool, but you shouldn’t need to use them very often. Use them when it makes the code clearer, not if it hides what is really happening. Sometimes it is better to check a condition with a separate method rather than do something like above.
Nullable types also have very very slight performance costs. They take a tiny bit more memory, and a tiny tiny bit more speed. 99.99% of the time you won’t likely notice any difference whatsoever. However if you are storing or iterating through 1000s of objects, you might just want to stick with the regular, non-nullable, types.
Thank you for this- I had just run into an error last night with non-nullable types, solved it a different way, and now I found this. Very helpful.
LikeLike
Glad to hear it!
LikeLike