March 31 2020

Unity C#: Tuples — Improved (C# 7.0)


In this post, you’ll increase your programming productivity by learning how to use the improved version of Unity C# Tuples that came along C# 7.0.

Have you ever felt uncomfortable when returning multiple values from a function?

There’re just too many ways and programmers can’t seem to settle on one.

Today I show you a sexy alternative: the improved Unity C# tuples.

What Are Unity C# Tuples?

Tuples are just a simple C# data structure used to contain multiple variables.

It’s some sort of structure, only that it’s declared inline and used with few variables.

By using C# tuples, we aim to reduce code verbosity and increase readability.

In the previous post, I showed you how to use the improved syntax of the Unity C# out parameters to return multiple values.

Well, tuples is another method to return multiple values from a function.

Let’s see some examples on how to use Unity C# tuples with raycasting.

How to Use C# Tuples Since C# 7.0

For these examples, I’ll assume we have an array of RaycastHit lying around to use the Unity non memory-allocating raycasting functions. I save this field in my testing class to store up to six raycast hit results:

readonly RaycastHit[] _tmpRaycastHits = new RaycastHit[6];

Before C# 7.0, the official way to create tuples was by using the generic Tuple class like so:

return new Tuple<int, RaycastHit[]>(numHits, _tmpRaycastHits)

But now with C# 7.0 we have leveled up the game.

Come check this out.

Basic C# Tuple

This is the most basic form of a C# 7.0 tuple:

(int, RaycastHit[]) GetEnemiesInSight()
{
  int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _raycastHits);
  return (numHits, _tmpRaycastHits);
}

void TestTupleBasic()
{
  var enemiesInSight = GetEnemiesInSight();

  Debug.Log($"{enemiesInSight.Item1} enemies in sight:");
  foreach (var raycastHit in enemiesInSight.Item2)
  {
    Debug.Log(raycastHit.collider.gameObject.name);
  }
}

That’s essentially a modern C# tuple:

  • Line 1: You declare its type with (type1, type2, …)
  • Line 4: You create a specific tuple like (var1, var2, …)
  • Lines 11, 12: You access its elements like myTuple**.Item1, myTuple.Item2, …

This is already a big improvement over the old way, which feels quite natural to programming.

Now, I see some your concerns growing with this syntax for one specific reason: naming.

Which naming, you may ask?

Exactly. There’s no naming at all in the example.

Many people would go for C# structures for this reason.

Only that… There’s actually a way to get names going with tuples.

Named Unity C# Tuples

Here’s how you name your tuples’ elements:

(int numHits, RaycastHit[] raycastHits) GetEnemiesInSightNamed()
{
  int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _tmpRaycastHits);
  return (numHits, _tmpRaycastHits);
}

void TestTupleNamed()
{
  var enemiesInSight = GetEnemiesInSightNamed();
  
  Debug.Log($"{enemiesInSight.numHits} enemies in sight:");
  foreach (var raycastHit in enemiesInSight.raycastHits)
  {
    Debug.Log($"I see you, {raycastHit.collider.gameObject.name}");
  }
}

The syntax then becomes:

  • Line 1: You declare its type with (type1 name1, type2 name2, …)
  • Line 4: You create a specific tuple like (var1, var2, …)
  • Lines 11, 12: You access its elements by their new names

Much better now that it’s more clear what each variable does.

But don’t stop here, we can keep improving!

The next step is Tuple Deconstruction.

Or how I call it, destruction.

Unity C#: Tuple Deconstruction

As a reminder, here’s the basic function we started with:

(int, RaycastHit[]) GetEnemiesInSight()
{
  int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _raycastHits);
  return (numHits, _tmpRaycastHits);
}

Now, here’s the news: the caller of that function can “deconstruct” the tuple in different ways to increase clarity and reduce verbosity further.

Deconstructing simply means to alter the naming of the tuple that the function returns to better suit the context or style of the function caller.

In other words, we destroy the plans the function programmer had for us. We use it our way.

Sounds abstract? That’s fine. It’ll become clear with a few examples.

(A) First, the caller can rename the elements of the tuple like this:

(int numEnemiesInSightRenamed, RaycastHit[] tmpRaycastHitsToAggro) enemiesInSight = GetEnemiesInSight();
Debug.Log($"(A) Number of enemies in sight: {enemiesInSight.numEnemiesInSightRenamed}");

(B) Second, we can skip declaring the tuple and instead declare its elements directly:

(int numEnemiesInSightRenamed, RaycastHit[] tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(B) Enemies in sight: {numEnemiesInSightRenamed}");

(C, D) Finally, you may also use type inference to keep your code shorter:

var (C_numEnemiesInSightRenamed, C_tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(C) Enemies in sight: {C_numEnemiesInSightRenamed}");

(var D_numEnemiesInSightRenamed, var D_tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(D) Enemies in sight: {D_numEnemiesInSightRenamed}");

There are more options to use tuples, but adding them all would only confuse you.

So let’s stick to the basics for now.

What’s Next?

Tuples are a fast way of returning multiple values while reducing programming boilerplate.

There’re many ways to do this, so find a style you like and try it out in your project.

See what you like and what you don’t.

Unity C# Tuples have given me a considerable productivity boost in programming.

And they surely look elegant.

Just remember: while your performance as a programmer is important, that won’t help unless your game runs in a performing way in your players’ devices.

So the next step is to check out my Unity performance checklist to optimize your game and avoid 1-star reviews due to poor frame-rate.

~Ruben

The Gamedev Guru Logo

Performance Labs SL
Paseo de la Castellana 194, Ground Floor B
28046 Madrid, Spain

This website is not sponsored by or affiliated with Facebook, Unity Technologies, Gamedev.net or Gamasutra.

The content you find here is based on my own opinions. Use this information at your own risk.
Some icons provided by Icons8