0
\$\begingroup\$

We know Vector3 is a struct and for this reason it can't be null. Let's suppose that I want to rotate to a enemy position, if I use a Vector3 I can't be sure if there was an enemy or not and maybe I will be looking at Vector3.zero. A solution for this problem is use the enemy transform (and not the position) and do a null check, but another option is make a nullable Vector3, but I'm not sure which use, what should I take in account to take a decision?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I'm pretty sure I don't understand what you're asking. Why does it matter if there is an enemy or not, and how do you not know if you have its position? \$\endgroup\$
    – Almo
    Commented Sep 27, 2017 at 15:36
  • \$\begingroup\$ Agreed; if you don't know "if there is an enemy," where are you getting the target position to try to rotate towards? This sounds like a problem you want to solve at a higher level rather than introduce reference types (and thus potentially produce more garbage for the collector than you need). \$\endgroup\$
    – user1430
    Commented Sep 27, 2017 at 15:54

1 Answer 1

0
\$\begingroup\$

Let's suppose that I want to rotate to a enemy position, if I use a Vector3 I can't be sure if there was an enemy or not

This should be an impossible case. To ensure it's impossible, design your interface accordingly. You should be writing this function:

Quaternion findRotationToFace(Character subject, Character target)

This function takes the subject character (could be the player) and computes the rotation necessary to face the target character (could be an enemy), returning that rotation as a quaternion. Or any other rotation representation you prefer, such as a Transform, if that's really what you want.

Then there's no ambiguity: this function must have two characters to work, and if you don't have a second character you can't call it. So in higher-level code you can write:

if (enemy != null) {
  // An enemy exists for the player to look at, so face it:
  Quaternion rotation = findRotationToFace(player, enemy);
  player.rotateBy(rotation);
} else {
   // No enemies right now.
   player.twiddleThumbs();
}

To answer your broader question of when to use a nullable thing versus not, there's three primary things you'll want to consider:

  • Nullability adds complexity; it means the null case must be checked for. Ideally, one would prefer simple interfaces over complex ones, so if you can avoid scenarios where null data can be passed or returned, that's often better. In other words, use nullability when you have to.
  • Examples of cases where you might "have" to have a null are scenarios where all valid values of some type are "correct" outputs. For example, the classic function int stringToInteger(string input) function: there is no sentinel value like 0 or -1 you can choose to represent the case where input contains "hello" instead of a number, so a Nullable<int> might be a good choice for a return value (ignoring the other possibilities for communicating errors, like throwing exceptions).
  • For C#/Unity specifically: remember that reference types exist on the heap and thus create garbage for the collector to process. For C# games, it's usually preferable to minimize the amount of incidental, temporary garbage so that you minimize the tendency of the GC to run its gen0 phase on junk, pushing things you do really need to have on the heap into later, more expensive-to-collect generations. Nullable<T> is itself a value type and thus not afflicted with this, but other nullable things are generally reference types, so this does apply to those.

The tl;dr is that you should usually use non-nullable types when you can, but nullable ones when you have to.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .