I’ve been using this technique for a year or so and it’s awesome. Sadly it’s not something you’d ever find unless you knew to look for it, but I’d like more people to know and use this. It works beautifully for any situation where you have multiple lines of code that must stay together, but which have to remain separate – e.g. an API that requires you to call “manager.Begin();” … then your own code, then … “manager.End();”
We’re going to commandeer the “using” keyword for something it wasn’t originally designed for but which is a 100% legal use of it.
Have you ever needed to do this in C#?
In Unity there are some very common examples of this problem, most famously in the old GUI.* API (which is finally, slowly, being replaced by UIToolkit – but there’s a lot of GUI.* code still in live games).
GUILayout.BeginVertical(); GUILayout.Label( "Options" ); if( GUI.Button( "Option1" ) ) { Process1(); } if( GUI.Button( "Option2" ) ) { Process 2(); } GUILayout.BeginHorizontal(); GUILayout.Space( 20 ); GUILayout.BeginHorizontal(); GUILayout.Label( "[advanced]" ); if( GUILayout.Button( "Option3" ) ) { Process3(); } ... and now I have to remember which order to EndHorizontal, EndVertical, and how many times for each
This is a very simple example and yet already it’s both timeconsuming to type all those “GUI.EndHorizontal/EndVertical” (the IDE cannot autocomplete them for you because it has no idea what you want here) and also highly error-prone.
Problems: Code together … but apart
It’s annoying trying to remember all the bits you’ve Begin’d but not yet End’d, but the real problem comes when you need to edit that code, inserting another piece of embedded horizontal layout halfway down.
Or when you copy/past a chunk of it and try to re-use it elsewhere.
You might try to be smart and move this stuff to a method call, but … that quick becomes painful for two reasons:
- You need to generate method calls and insert them in your sourcecode based on context. You can do that, but now you’re having to pass-around functionpointers, it’s no longer a simple method.
- You don’t know which order they’ll happen in, or how many times, so now you also need to create a stack object to keep track of this. You also have to implement all the cases of Exceptions etc and making sure the stack unwindws correctly, and … and …
Solution: IDisposable / using{}
C# has a nice little feature that fixes all of this. It converts my code above into:
using( new Vertical() ) { GUILayout.Label( "Options" ); if( GUI.Button( "Option1" ) ) { Process1(); } if( GUI.Button( "Option2" ) ) { Process 2(); } using( new Horizontal() ); { GUILayout.Space( 20 ); using( new Horizontal() ); { GUILayout.Label( "[advanced]" ); if( GUILayout.Button( "Option3" ) ) { Process3(); } } } }
Two things have happened here:
- Improvement 1: We never have to remember to End() anything!
- Improvement 2: All code is now surrounded in {braces} making it much easier to read, and to edit safely!
Both of them are side-effects of IDisposable/using.
How to implement it
You have to create a class that holds the magic code. In the GUILayout.BeginHorizontal example I made a class “Horizontal”. This class has to implement Microsoft’s IDisposable interface – and there’s plenty of docs online for how to do this, it’s quite easy. Here’s a simple example:
class Horizontal : System.IDisposable { public Horizontal() { GUILayout.BeginHorizontal(); } public void Dispose() // but read WARNING below { GUILayout.EndHorizontal(); } }
The way that “using” is implemented by Microsoft is:
- The object is created as normal (when you call ‘new’ in “using( new Horizontal();”)
- The code in braces runs as normal
- When the close brace is encountered, MS destroys the temporary object you created
- …which has the side effect of calling “Dispose” on that object
- All of this is managed for you, and copes well with exceptions etc
WARNING: the simple example isn’t quite correct
If you subclass your Horizontal object this may start to go wrong because of how Dispose works. This is documented on MS’s official pages, but the simple explanation is that “Dispose” can be called more than once on the same object (when they’re subclassed), so you need to add some code to ignore the extra calls. Here’s the classic example:
class Horizontal : System.IDisposable { public Horizontal() { GUILayout.BeginHorizontal(); } public void Dispose() // perfect example { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { GUILayout.EndHorizontal(); } // Indicate that the instance has been disposed. _disposed = true; } } }
Performance optimization: ZERO garbage-collection!
If you care about performance then you typically want to make your core methods non-allocating. Creating temporary objects like in the above examples has no effect most of the time – but if you ever use it on a core method that you call tens of thousands of times a frame (note: anything less than tens of thousands and you probably won’t notice, unless your game is very performance-heavy) it’ll start to create enough garbage that GC becomes a problem.
We can fix that, and get rid of the double-dispose problem, by converting it to a struct.
Unfortunately … C# doesn’t allow structs to have a zero-argument constructor, so we have to add at least one fake parameter to make this work. In most cases there’s some obvious parameter you can think of that improves your implementation. e.g. here I’ve added the same optional param that GUI.BeginHorizontal has:
struct Horizontal : System.IDisposable { public Horizontal( params GUILayoutOption[] options ) { GUILayout.BeginHorizontal( options ); } public void Dispose() // but reading WARNING below { GUILayout.EndHorizontal(); } }