C# Discards: Make It Crystal Clear (C# 7.0)

Published May 05, 2020 by Ruben Torres Bonet (The Gamedev Guru)
Do you see issues with this article? Let us know.
Advertisement

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)

Quick Navigation

What Are Unity C# Discards?

How to Use Unity C# Discards

Calling C# Methods, Ignoring Out Parameters

Discarding Unity C# Tuple elements

Standalone C# Discards

Do Unity C# Discards Make a Performance Difference?

So, What's Next?

What Are Unity C# Discards?

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.

How to Use Unity C# Discards

Calling C# Methods, Ignoring Out Parameters

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.

Discarding Unity C# Tuple elements

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.

Standalone C# Discards

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:

  • Did they forget to use the return value? Let's investigate, versus...
  • Okay, they don't care about it. Let's move on.

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.

Do Unity C# Discards Make a Performance Difference?

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:

Unity C# Discards Tuple: IL2CPP Comparison

Unity C# Discards Tuple: IL2CPP Comparison

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.

So, What's Next?

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

Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

Featured Tutorial

Let's avoid programmers' misunderstandings that often escalate to fist fights. 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).

Advertisement
Advertisement
Advertisement