Notifications
Article
GetComponent() only once!
Updated 5 months ago
356
3
Repeated calls of GetComponent() within a single block of code or condition is a code smell. It hurts performance, it makes the code hard to read and it's frequently a tell-tale sign of copy/paste programming, which will look bad in your job application. Read this article to improve your code style to make your code easier to debug, understand, maintain and thus viewed more favourable by employers.
Discuss this Article on the Unity Forum...

A Bad Example

Let me start by showing you a bad example, then I'll discuss the issues within that code fragment and how to improve it. Consider the following Update() function:
void Update() { foreach (var gameObject in someList) { if (gameObject.GetComponent<Head>() != null && gameObject.GetComponent<Head>().Visible && gameObject.GetComponent<Torso>() != null && gameObject.GetComponent<Torso>().Visible && gameObject.GetComponent<Limbs>() != null && gameObject.GetComponent<Limbs>().GetComponentsInChildren<Limb>() != null && gameObject.GetComponent<Limbs>().GetComponentsInChildren<Limb>().Length == 4) { gameObject.GetComponent<Head>().Wobble(); gameObject.GetComponent<Torso>().Shake(); foreach (var limb in gameObject.GetComponent<Limbs>().GetComponentsInChildren<Limb>()) { if (limb != null && limb.Type == Limb.Arm) limb.Swing(); else if (limb != null && limb.Type == Limb.Leg) limb.Dance(); } } } }
So, what's so bad about this code? See if you can catch all the issues (and perhaps find even more than I did).
Here's the issues I have with this code:
  1. It's extremely verbose. One cannot just glimpse over and immediately grasp the purpose of this code, one is forced to take a close look in particular at the if condition.
  2. It's repetitive, both gameObject and GetComponent appear many times, significantly increasing noise.
  3. Each GetComponent call has a performance overhead, unnecessary extra work is performed which could possibly hurt framerate and will definitely drain thee battery quicker on mobile devices.
  4. Null checks could be considered superfluous if we assume that all characters are supposed to have a Head, Torso and four Limbs components.
  5. One may wonder why Head and Torso need both be Visible for the inner loop to be processed.
  6. The assumption that each character has 4 limbs will cause any future expansion for characters with more or less limbs to skip this code altogether, which doesn't seem desirable.
Side note courtesy of Fido789: getting rid of GetComponent calls altogether in Update() or other frequently called methods is something to strive for in general. In this pseudocode based on actual real world example it wasn't sensible in the context of this article. But generally speaking, rather than having a list of game objects and calling GetComponent on them in Update() one would normally iterate over collections of components to begin with (if feasible).

Caching Components locally

Let's clean up this code. First I'll focus on caching references locally to avoid repeated calls to GetComponent and null checks as much as possible. Notice how this simple improvement already improves the code's readability a lot, and it's wasting fewer CPU cycles as a bonus.
void Update() { foreach (var gameObject in someList) { // cache components locally var head = gameObject.GetComponent<Head>(); var torso = gameObject.GetComponent<Torso>(); var limbsContainer = gameObject.GetComponent<Limbs>(); var limbs = (limbsContainer != null ? limbsContainer.GetComponentsInChildren<Limb>() : null); if (head != null && head.Visible && torso != null && torso.Visible && limbs != null && limbs.Length == 4) { head.Wobble(); torso.Shake(); foreach (var limb in limbs) { if (limb == null) continue; if (limb.Type == Limb.Arm) limb.Swing(); else if (limb.Type == Limb.Leg) limb.Dance(); } } } }
All of a sudden, just by removing the repeated GetComponent() function calls by caching their results in local variables the code has become a lot cleaner and easier to follow.

Rewriting the code to be more sensible

However, there's still the question: why can't heads Wobble unless the torso is visible, and vice versa? And can we get rid of the four limbs requirement?
Let's assume that by reviewing the intentions of the design we determined that:
  1. All characters have a head, torso and limbs.
  2. Human characters always have four limbs, where missing limbs are simply set to be invisible.
  3. We want to retain the option of adding monsters in the future, which may have more or less than four limbs. The design requires us to add a limb type enum (ie Limb.Tentacle) to differentiate between limbs that can swing, dance or whatever else they may need to do.
  4. The limbs for all characters are always in a child object of a character.
  5. Characters without a head should still be able to dance. Consider Monkey Island 2 as reference.
  6. None of the characters will have its torso set to be invisible. And even if we had an invisible torso the character should still be able to dance.
  7. If a body part is not visible it should not animate to save CPU cycles.
Following that we can further correct and simplify the code at the same time:
void Update() { foreach (var gameObject in someList) { var head = gameObject.GetComponent<Head>(); if (head.Visible) head.Wobble(); var torso = gameObject.GetComponent<Torso>(); if (torso.Visible) torso.Shake(); var limbs = gameObject.GetComponent<Limbs>().GetComponentsInChildren<Limb>(); foreach (var limb in limbs) { if (limb.Visible == false) continue; switch (limb.Type) { case Limb.Arm: limb.Swing(); break; case Limb.Leg: limb.Dance(); break; case Limb.Tentacle: limb.Slash(); break; default: break; } } } }
This final example uses the minimal amount of code that does exactly what is required, no more, no less.
Note that this is pseudocode. Here the Limb component implements all types of limb animations. In production code you might have separate components for each type of limb implementing their Animate() method differently.
Since we know that we are supposed to get a a character with head and torso, we will get a NullReferenceException. This is desirable because clearly it's a bug that should not go unnoticed, even though it can be annoying at times. This also makes the code slightly faster because null checks for UnityEngine objects have a higher performance impact than you might expect.
One last thing to note: if you needed to debug (ie step over) the last code fragment you will find this to be a lot easier than in the first example. Consider the if condition at the beginning - if the code skips the inner block, could you tell which of these conditions equated to false? Whereas the final example clearly tells you what went wrong while stepping over each line!
Take a few seconds to compare the initial, bad example with the last example code fragment to appreciate how the code improved. Then go back to your own code and check if you can apply what you've just learned to your own code.

Further Reading

Caching calls to GetComponent() in local variables or class fields (aka members) is also recommended by Unity's Optimizing Scripts article in the Learn section of the Unity website.
Discuss this Article on the Unity Forum...

Tags:
Steffen Itterheim
Unity Enthusiast, Team & Tech Lead - Manager
7
Comments
unity.ref.ma
4 months ago
Maroc Unity - Manager
Thanks.
0
Steffen Itterheim
5 months ago
Unity Enthusiast, Team & Tech Lead - Manager
Glad you like it. Happy that you got something out of it. :)
0
TD
Tim Duval
5 months ago
Very informative! Ten seconds into clicking follow and I already learned three new things.
0