May 04 2020
In this post, I’ll show you how you can make your code more explicit and simpler by using the Unity C# Discards feature (C# 7.0)
Discards are a C# 7.0 language feature that helps you making your programming intentions more explicit, clearer and simpler.
With discards, you’ll help others understand the intentions behind your code in some programming scenarios.
Often enough in programming, you must name some of your variables even if you don’t really need them.
This happens, for instance, when you call a function with out parameters that you don’t need.
Well, C# discards let you explicitly ignore the variables you don’t care about, making that clear to other developers as well.
You do discards in C# by using the underscore symbol instead of a variable name, as we’re about to see.
Imagine you’re doing a raycast to test whether something is visible or not.
Using Physics.Raycast, that’s it.
You don’t care about whom you hit. You just need the boolean the function returns.
So, instead of declaring an out parameter that you don’t need, you use a discard like this:
var cosmicRay = new Ray(transform.position, transform.forward);
if (Physics.Raycast(cosmicRay, out _))
{
Debug.Log("I can see you");
}
Instead of naming a variable, we use the underscore symbol to make it clear to the programmer we don’t care about the out parameter.
We can also use discards in tuples, by the way.
If you have a function that returns a tuple, then you won’t need all of its elements all the time.
The solution? Discard the tuple elements you don’t care about.
Here’s an example of a function that returns a tuple and a call that ignores the second tuple element (line 7).
(int numHits, RaycastHit[] raycastHits) GetEnemiesInSight()
{
int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _tmpRaycastHits);
return (numHits, _tmpRaycastHits);
}
(var enemiesInSight, _) = GetEnemiesInSight();
You can also use discards with functions that return a single value.
If you don’t care about the return value of a function, you can also discard it:
_ = Physics.RaycastNonAlloc(transform.position, transform.forward, _tmpRaycastHits);
Now, when I did this, I asked myself…
Why the hell would I do this instead of just not declaring it?
Well, here’s the answer: by using a discard, you make it clear you don’t care about it.
You make it clear that you made a conscious choice not to use the return value.
You show the other developer that you didn’t sloppily forget about it.
And you tell to your future self: I did this for a reason, so don’t touch it.
You see, making things more explicit will save you and others a lot of thinking time.
It’s the difference between:
Do you see the difference it makes in programmers’ minds?
And, by using discards, you’ll teach other developers new features they probably didn’t know about.
They’ll be prepared for when they read the source code of other projects or plugins.
Not much, I’d say.
But let’s elaborate.
A) When you pass a discard an out parameter, that doesn’t really change things much. This is so, because C# still has to pass a parameter anyway.
Unity reserves memory for a discard variable and passes it to the function under the hood. Otherwise, the C++ function could crash your process if you passed a null, right?
B) However, discarding tuple elements does actually make a small difference.
If you discard a component of a tuple returned by a function, C# won’t allocate memory for it on the stack and won’t “rescue” that value from the function call.
Here’s a comparison that includes the IL code as well.
Raycast Hits Discarded
(var enemiesInSightDiscard, _) = GetEnemiesInSight();
IL_0055: ldarg.0 // this
IL_0056: call instance valuetype [mscorlib]System.ValueTuple`2<int32, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]> Ep03Discards::'<Update>g__GetEnemiesInSight|2_0'()
IL_005b: ldfld !0/*int32*/ valuetype [mscorlib]System.ValueTuple`2<int32, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]>::Item1
IL_0060: stloc.2 // enemiesInSightDiscard
Raycast Hits NOT Discarded
(var enemiesInSightFull, var raycastHitsNewFull) = GetEnemiesInSight();
[4] valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[] raycastHitsNewFull // Allocated on the stack
IL_0061: ldarg.0 // this
IL_0062: call instance valuetype [mscorlib]System.ValueTuple`2<int32, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]> Ep03Discards::'<Update>g__GetEnemiesInSight|2_0'()
IL_0067: dup
IL_0068: ldfld !0/*int32*/ valuetype [mscorlib]System.ValueTuple`2<int32, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]>::Item1
IL_006d: stloc.3 // enemiesInSightFull
IL_006e: ldfld !1/*valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]*/ valuetype [mscorlib]System.ValueTuple`2<int32, valuetype [UnityEngine.PhysicsModule]UnityEngine.RaycastHit[]>::Item2
IL_0073: stloc.s raycastHitsNewFull
As you can see, if we do not discard the variable, Unity reserves memory for the raycast hits array on the stack.
And Unity doesn’t reserve memory if you discard the tuple element.
Yes, even the C++ code in master-mode IL2CPP shows the added overhead for not using discards.
Let me show you:
The remaining question is…
Will the final C++ compiler optimize the non-discarded tuple elements?
It depends.
In debug builds where you do not optimize your C++ code, the compiler won’t optimize your build.
So your debug builds will run (a tiny bit) slower by not using discards on tuple elements.
In release builds, the C++ compiler optimizes as much as possible. So, it is highly likely discards won’t be of much help.
Here’s my take: use discards where possible, but don’t get obsessed about it.
C# discards make your programming intentions clearer.
Discards will help you avoid misunderstandings. You know some of these conversations can be very passionate/
Try discards and see the subtle difference it makes in your project.
If you want more Unity Performance Tips, get my Unity Performance Checklist.
~Ruben