Memory management and C# do's and don'ts?

For the more experienced people out there, are there any helpful tips on what you should and shouldn't do inside of C# in the Unity environment?

For example:

  • Is there anything wrong with setting a game object/component/etc to be pointed to by a static variable in some class? What happens when you Destrory() that game object that is being pointed to by the static variable?
  • Is there any reason to not have helper classes set up that aren't monobehaviours or their derived class?
    • If not, if a game object contains one of those standalone classes, do those objects get garbage collected properly when the parent object gets destroyed?
  • Are there any "gotchas" when using Delegates? Similarly to the above, if you delete a game object that a Delegate (in another object) is referencing, will the game object not get cleaned up?
  • What if you try to use StartCoroutine from one object to point to a IEnumerator-returning function in a different object? What about if that second object isn't a monobehaviour?

Oh boy, long topic.

Is there anything wrong with setting a game object/component/etc to be pointed to by a static variable in some class? What happens when you Destroy() that game object that is being pointed to by the static variable?

This is a general .Net question - basically, objects exist until the last reference is gone, no matter if that reference was in a static or an instance property. If you Destroy an object that is being referenced by another class, it's likely that an exception will be raised the next time the other class attempts to access that Game Object.

Give it a shot and document what happens. :-)

Is there any reason to not have helper classes set up that aren't monobehaviours or their derived class?

No particular reason, no. On the contrary, I actually like to keep as much code as classes instead of MonoBehaviors, for several reasons:

  1. I cannot easily write unit tests for MonoBehaviors, whereas I can do so for classes.
  2. They may be re-used outside of Unity.
  3. MonoBehaviors have a certain overhead that you may not want for anything that doesn't explicitly need to sit on top of the engine.

If not, if a game object contains one of those standalone classes, do those objects get garbage collected properly when the parent object gets destroyed?

There is no reason why those classes shouldn't be garbage-collected, other than a bug in the Mono GC.

Are there any "gotchas" when using Delegates? Similarly to the above, if you delete a game object that a Delegate (in another object) is referencing, will the game object not get cleaned up?

I have no reason to believe that the GC will not behave as expected, but see above about using destroyed references.

In general, the main gotcha I've found that people run into is that they tend to forget that even if they're running in a memory-managed environment, they should still think about allocation. If you go crazy with allocating and de-allocating large chunks of memory, chances are that you'll end up in a situation that:

  1. Triggers the garbage collector more often, which will have an impact on performance, and;
  2. End up with a fragmented heap and growing memory needs, since Mono's Garbage Collector still doesn't compact the heap by default (and on top of that, Unity's using an old Mono version).

Keep your allocations small, and if you allocate a large buffer consider re-using it instead of destroying it (for instance, wiping out a large matrix of values and using the same one, instead of re-allocating different sizes across iterations).

More on memory fragmentation in Mono.

Are there any "gotchas" when using Delegates? Similarly to the above, if you delete a game object that a Delegate (in another object) is referencing, will the game object not get cleaned up?

There's one catch with this case: calling delegates on MonoBehaviours that are Destroy()ed but not yet cleaned up (i.e., in the frame between the Destroy() call and the actual removal of the object from memory). This will cause errors if you then try to call any Unity API functions on that object inside the delegate.

The simplest fix I've found is to wrap such delegates in an if(this) block, like so:

public void MyDelegateFunction() {
    if(this != null) {
       AddComponent(typeof(Light));
    }
}

It looks like MonoBehaviours and other Unity objects override == to behave as if they are null when they've been destroyed, which is why this works (in normal C# context, this != null is nonsensical).

Worth a mention...

C#/CIL does support value types. If you declare an object as a "struct" it will be allocated on the stack and copied by value, just like primitives. (The only exception is occasions where the value needs to be "boxed", e.g. if you reference it with a vanilla "object" handle type).

Lots of Unity's small object types (e.g. Vector3) are structs and don't affect garbage collection at all (except indirectly when they're used as instance members). So the following:

Vector3 position = new Vector3(0,10,0);

Doesn't actually cause a heap allocation, it just ensures that the memory is initialized (Lippert's blog "Adventure's in Coding" goes into more detail about this).

If you have small types, you can declare them as structs and take advantage of this. Just don't go crazy - a struct with 100 members will be a lot slower than a class (imagine each function call as taking 100 arguments :P).

Is there anything wrong with setting a game object/component/etc to be pointed to by a static variable in some class?

Although there's nothing technically wrong with referencing a game component in a static variable, in my experience this is almost always a bad idea. There's usually a way to refactor your code to avoid it.

This blog post gives many reasons why static state is usually a bad idea. For me, the biggest reason comes down to re-entrancy.

Now, is there a place for statics? In my experience, they can be convenient in the following situations:

  • Storing stateless objects (such as an object that only contains methods, but no fields) to avoid having to instantiate them repeatedly. When doing this, however, I normally pass such objects to the methods which use them, rather than accessing the statics directly in logic. This allows callers to pass in different objects if they want to.
  • Storing constants (i.e. objects that will never change state). However, be careful that the constants don't represent assumptions that might change if multiple copies of the code were running at the same time.

I am currently trying to work around a memory leak by using static variables. It seems that accessing member variables in a certain context (access by delegates run by threads in a thread pool), the object’s memory is never released. I haven’t finished the workaround yet, but in my test, the problem doesn’t occur if no non-static data members are accessed.