Basic

May 31 2020

5 comments

Unity C# 7.0: Reference Locals & Returns

By Rubén Torres Bonet

May 31, 2020


__CONFIG_colors_palette__{"active_palette":0,"config":{"colors":{"6c4de":{"name":"Main Accent","parent":-1},"67ed2":{"name":"Accent Dark","parent":"6c4de","lock":{"saturation":1}}},"gradients":[]},"palettes":[{"name":"Default","value":{"colors":{"6c4de":{"val":"var(--tcb-skin-color-0)"},"67ed2":{"val":"rgb(59, 65, 63)","hsl_parent_dependency":{"h":160,"l":-0.05,"s":0.04}}},"gradients":[]},"original":{"colors":{"6c4de":{"val":"rgb(51, 190, 127)","hsl":{"h":152,"s":0.57,"l":0.47}},"67ed2":{"val":"rgb(59, 65, 63)","hsl_parent_dependency":{"h":160,"s":0.04,"l":0.24}}},"gradients":[]}}]}__CONFIG_colors_palette__

In this article, I'll show you how to apply Unity C# 7.0: ref locals & returns to simplify your game code and make your programming intentions clear when you deal with C# value types.

1. What a Bloody Mess!

Let's say you're developing a MMO (somehow a favorite for new game developers) and you're currently programming a cheat to level up your character.

You know, faster development iterations.

So you have your PlayerStats structure, and also a Player class that contains its name and an instance of PlayerStats:

class Player
{
  public string name;
  public PlayerStats stats;
}
struct PlayerStats
{
  public int level;
  public int wrinkles;
}

Why a structure?

In my opinion, it generally makes sense to pack data into structures* because of the benefits of value types:

  • Being value types, they are copied most of the time when passing it around. Your data remains more protected.
  • Value types are often placed in the stack, meaning that you alleviate some of the pressure the garbage collection suffers from.

*Classes vs structures in game development is a tricky topic in itself. If you want to discuss that further, I'd be more than happy to: send me an e-mail to [email protected].

In any case, this post not about this use case. It is about how this C# feature can benefit your use case.

Our cheat function will level up the character by increasing the player stats the following way:

  • We add one to the current level.
  • We add a few nasty wrinkles on the character's face.

I know, very original gameplay mechanics that you can copy for your next MMO.

So your level up cheat function looks like this:

private void Cheat_LevelUpPlayer()
{
  const string playerName = "Duncan";

  var youngDuncan = GetPlayerStats(playerName);
    
  Assert.IsTrue(youngDuncan.wrinkles == 0, "Young Duncan should have no wrinkles!");
    
  // Level up
  youngDuncan.level++;
  youngDuncan.wrinkles += 5;
    
  var oldDuncan = GetPlayerStats(playerName);
  Assert.IsTrue(oldDuncan.wrinkles > 0, "Old Duncan should have at least a wrinkle!");

  Debug.Log("Duncan aged successfully");
}

And this is the function that gives you the player stats:

private PlayerStats GetPlayerStats(string playerName)
{
    var player = _players.Find(searchedPlayer => searchedPlayer.name.Equals(playerName));
    return player.stats;
}

Now, if you know the difference between value types and reference types, you'd be right at saying "this won't work as you expect, my friend".

PlayerStats is a value type (it's a structure). That means, C# will copy that instance for you when you return its value. Just to be safe.

So what happens is simple: no matter how you change the youngDuncan variable, it will never affect the original value stored in your list of players.

Traditionally, there are a few options to modify the values of PlayerStats:

  • You can access the list directly by asking for an index instead of for a value.
  • You can also declare a second parameter of type "ref PlayerStats".

Those options will make your code dirtier.

And you don't want dirt at home.

2. Solving the Mess With Ref Locals & Functions

Here's an easier solution: just return a reference to the PlayerStats structure.

I know, PlayerStats is a value type (a structure) that is always copied when you pass it around.

But we can politely ask C# to return it as a reference.

Hey C#, would you mind giving me a reference instead of a value?

And it turns out it is  very straight-forward thing to do: just use the ref keyword when returning it and when receiving it.

See for yourself:

private ref PlayerStats GetPlayerStatsRef(string playerName)
{
  var player = _players.Find(searchedPlayer => searchedPlayer.name.Equals(playerName));
  return ref player.stats;
}

private void Cheat_LevelUpPlayer()
{
  const string playerName = "Duncan";

  ref var youngDuncan = ref GetPlayerStatsRef(playerName);
    
  Assert.IsTrue(youngDuncan.wrinkles == 0, "Young Duncan should have no wrinkles!");
    
  // Level up
  youngDuncan.level++;
  youngDuncan.wrinkles += 5;
    
  var oldDuncan = GetPlayerStats(playerName);
  Assert.IsTrue(oldDuncan.wrinkles > 0, "Old Duncan should have at least a wrinkle!");

  Debug.Log("Duncan aged successfully");
}

Here, we "just" added the ref keyword in four different places:

  • Line 1: ref before the return type of the function
  • Line 4: ref after the return keyword
  • Line 11: ref before the variable type and before the function invocation.

Yes, it's a bit over the top. I guess Microsoft really wanted us to be clear about our intentions.

If we now run the code, we will see that Mr. Duncan aged successfully.

Old Duncan has now a few wrinkles more... The price of experience.

3. Do C# Local References Make a Performance Difference?

Using references makes your code (slightly) cheaper, because we skip copying entire values.

Let's do a small comparison:

private void Compare()
{
  const string playerName = "Duncan";
  var youngDuncanCopy = GetPlayerStats(playerName);
  ref var youngDuncanRef = ref GetPlayerStatsRef(playerName);
    
  youngDuncanCopy.level++;
  youngDuncanRef.level++;
}

Let's translate the C# code into beautiful IL code:

Unity C# 7.0: copy vs reference function invocation (IL code)

Unity C# 7.0: copy vs reference function invocation (IL code)

The IL codes are very similar but they have subtle changes that make all the difference:

  1. The youngDuncanCopy variable is just that, a whole new PlayerStats structure. However, youngDuncanRef is a reference to another PlayerStats variable (the & symbol denotes reference).
  2. When calling GetPlayerStats, we ask for a copy of the variable the function returns (no &).
  3. When you call GetPlayerStatsRef, we are actually getting a reference to that variable (see the & symbol).

You might wonder: how different are the functions themselves?

Here's the answer:

  • GetPlayerStats (copy) uses the IL instruction ldfld to load the value of the stats field.
  • GetPlayerStats (ref) uses the IL instruction ldflda to load the address of the stats field.

So there you have it. A cheaper alternative at your disposal.

Lastly, be careful with this technique.

Using C# refs also makes your code unsafer, because now you have the power to modify the state of your game more easily.

Your choice.

What's Next?

If running your game at 60 FPS is something you would like, you'll love my Unity Game Performance Checklist.

This list has over 100 action items that you can execute on to increase the performance of your game.

Low performance is painful for your players and your wallet. So don't with anything below 60 FPS.

Grab my Unity Performance Checklist now.

...And start optimizing your game.

~Ruben

  • Could you please give an any example of justified use case. In your particular case looks like much more reasonable just to make PlayerStats a class instead of struct and don’t clutter the code with all this refs. Cause you don’t want dirt at home, right?

    • Actually, it’s a good idea to use structures to avoid messing with the heap, where possible, and to make your code safer on sensitive information (value types). Why would you go for a class?
      Don’t forget: the point is not my use case but how you can use this feature in your use case.

      • As I said, in this particular example I would go for a class just because PlayerStats will be in the heap and reference will be copied in both cases. So why to bother with cluttering the code with unnecessary refs? Also, I believe that for 96% of other programmers out there reference types behavior is more obvious and “intuitive” and expected by default from most of the types. So I think all this refs only adds complexity for readers with the same result exactly. Also messing with value types and passing them by reference often leads to not obvious bugs and even performance drops. Right now I can’t find the link to great article where a guy explains how you could end up with copying value type twice instead of once in attempt to don’t copy it at all by passing it by reference. But, if I remember correctly, it was with using the “in” keyword not “ref”. (If I finally find it I’ll definitely share it here.) The point is that it is not so obvious as you may expect what compiler will do with all your value type refs.

        And it’s exactly why I’ve posted my previous comment: I can’t find justified use case in my head right now out of my practice. So maybe you know any real use case for it.

        While I typed this, the only case came to my mind is it is when I have no sources and can’t change struct to class.

        • Hi Serepa.
          Thanks for your comment, I like having these discussions.
          For this particular example, like you said, it’s fine to go for classes. However, this is all about the use cases that programmers deal with when using value types.
          In general, in game development I’d rather go for value types where possible for small data structures due to (1) data safety and (2) reduced heap allocations. This is a tricky topic; that’s my take on this.
          Let me know if you find the link to that article, I’d love to read it.
          If you’d like to continue this conversation, we can gladly set up a chat. Just send me an email: [email protected]
          Ruben

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Rubén Torres Bonet

    About the author

    Born Game Developer, now ready to help you develop better games. Primary programmer on Star Trek Bridge Crew (Oculus Quest), Diamond Dash. Programmer on Time Stall, Catan Universe, Anne Frank House VR, Jelly Splash, Blackguards Definitive Edition. I also worked in minor XR experiences for HoloLens and Vive for clients such as Audi and Volkswagen.

    You might also like

    Simplify Your Life With Unity Mesh Simplifier
    >