Unity uses the open-source .NET platform to ensure that applications you make with Unity can run on a wide variety of different hardware configurations. The .NET platform supports a range of languages and API libraries.
Unity has two scripting backends; Mono, and IL2CPPA Unity-developed scripting back-end which you can use as an alternative to Mono when building projects for some platforms. More info
See in Glossary (Intermediate Language To C++), each of which uses a different compilation technique:
The benefit of using a JIT-based scripting backend is that the compilation time is typically much faster than AOT.
By default, Unity uses the Mono backend on platforms that support Mono. When you build a player for your application, you can choose which scripting backend to use. To do this through the Editor, go to Edit > Project Settings > Player, open the Other Settings panel, then click on the Scripting Backend dropdown and select which backend you want. For more information, see Scripting backendsA framework that powers scripting in Unity. Unity supports three different scripting backends depending on target platform: Mono, .NET and IL2CPP. Universal Windows Platform, however, supports only two: .NET and IL2CPP. More info
See in Glossary.
When you build an application, Unity compiles and then searches the assemblies (.DLLs) in your project to detect and remove unused code. This process of stripping code reduces the final binary size of your build, but increases build time.
Code stripping is disabled by default when you use Mono but code stripping can’t be disabled for IL2CPP. You can control how much code Unity strips with the Managed Stripping Level property.
To change this property, go to Edit > Project Settings > Player, open the Other Settings panel, then click on the Managed Stripping Level dropdown and select a stripping level.
As you increase the Managed Stripping Level, Unity removes more code. This increases the risk that Unity might remove code that your application relies on, especially if you use reflection or generate code at runtime.
You can use annotations on certain elements of your code to prevent Unity from stripping it. For more information, see Managed Code Stripping.
Unity uses the Boehm garbage collector for both the Mono and IL2CPP backends. Unity uses the Incremental mode by default. You can disable the Incremental mode to use stop-the-world garbage collection, although Unity recommends the Incremental mode.
To toggle between Incremental mode and stop-the-world, go to Edit > Project Settings > Player, open the Other Settings panel and click on the Use incremental GC checkbox. In Incremental mode, Unity’s garbage collector only runs for a limited period of time and doesn’t necessarily collect all objects in one pass. This spreads the time it takes to collect objects over multiple frames and reduces stuttering and CPU spikes. For more information, see Managed memory.
To check the number of allocations and possible CPU spikes in your application, use the Unity Profiler. You can also use the GarbageCollector API to completely disable garbage collection in Players. When the collector is disabled, be careful to avoid allocating excess memory.
Unity supports many platforms and might use different scripting backends depending on the platform. The .NET system libraries require platform-specific implementations to work correctly in some cases. While Unity tries its best to support as much of the .NET ecosystem as possible, there are some exceptions to parts of the .NET system libraries that Unity explicitly doesn’t support.
Unity makes no performance or allocation guarantees for the .NET system libraries across Unity versions. Generally, Unity doesn’t fix any performance regressions in the .NET system libraries.
Unity doesn’t support the System.Drawing library and it isn’t guaranteed to work on all platforms.
The JIT compilation that the Mono scripting backend uses enables you to emit dynamic C#/.NET Intermediate Language (IL) code generation during the runtime of your application. The AOT compilationAhead of Time (AOT) compilation is an iOS optimization method for optimizing the size of the built iOS player More info
See in Glossary that the IL2CPP scripting backend uses doesn’t support dynamic code generation.
This is important to consider when you use third-party libraries, because they might have different code paths for JIT and AOT, or they might use code paths that rely on dynamically generated code. For more information on how to generate code at runtime, see Microsoft’s ModuleBuilder documentation.
Although Unity supports multiple .NET API profiles, you should use the .NET Standard API Compatibility Level for all new projects for the following reasons:
Other profiles can be useful if, for example, you need to provide support for an older existing application. To change the Api Compatibility Level setting, go to Edit > Project Settings > Player. Under the Other Settings heading, set the Api Compatibility Level to the desired setting.
For more information, see .NET Profile Support.
You should only use third-party .NET libraries that have been extensively tested on a wide range of Unity configurations and platforms.
The performance characteristics of JIT and AOT code paths in third-party libraries might be significantly different. AOT generally reduces startup times and is suited to larger applications for this reason but increases the binary file size to accommodate the compiled code. AOT also takes longer to build during development.
JIT adjusts at runtime based on the platform it’s running on, which can increase running performance at the cost of a potentially longer application startup time. As such, you should profile your application in both the Editor, and on your target platform. For more information, see Profiler overview.
You should profile the usage of your .NET system libraries on all target platforms because their performance characteristics might vary depending on the scripting backends, .NET versions, and profiles you use.
When you review a third-party library, consider the following areas:
Mono and IL2CPP internally cache all C# reflection (System.Reflection) objects and by design, Unity doesn’t garbage collect them. The result of this behavior is that the garbage collector continuously scans the cached C# reflection objects during the lifetime of your application, which causes unnecessary and potentially significant garbage collector overhead.
To minimize the garbage collector overhead, avoid methods such as Assembly.GetTypes and Type.GetMethods() in your application, which create a lot of C# reflection objects at runtime. Instead, you should scan assemblies in the Editor for the required data and serialize and/or codegen it for use at runtime.
UnityEngine.Object is a special type of C# object in Unity, because it’s linked to a native C++ counterpart object. For example, when you use a CameraA component which creates an image of a particular viewpoint in your scene. The output is either drawn to the screen or captured as a texture. More info
See in Glossary component, Unity stores the state of the object on the object’s native C++ counterpart, not on the C# object itself.
Unity doesn’t currently support the use of the C# WeakReference class with instances of UnityEngine.Object. For this reason, you shouldn’t use a WeakReference to reference a loaded asset. See Microsoft’s WeakReference documentation for more information on the WeakReference class.
When you use a method such as Object.Destroy or Object.DestroyImmediate to destroy a UnityEngine.Object derived object, Unity destroys (unloads) the native counter object. You can’t destroy the C# object with an explicit call, because the garbage collector manages the memory. Once there are no longer any references to the managed object, the garbage collector collects and destroys it.
If your application tries to access a destroyed UnityEngine.Object again, Unity recreates the native counterpart object for most types. Two exceptions to this recreation behavior are MonoBehaviour and ScriptableObject: Unity never reloads them once they have been destroyed.
MonoBehaviour and ScriptableObject override the equality (==) and inequality (!=) operators. If you compare a destroyed MonoBehaviour or ScriptableObject against null
, the operators return true when the managed object still exists and hasn’t yet been garbage collected.
Because you can’t overload the ?? and ?. operators, they aren’t compatible with objects that derive from UnityEngine.Object. The operators don’t return the same results as the equality and inequality operators when you use them on a destroyed MonoBehaviour or ScriptableObject while the managed object still exists.
The Unity API isn’t thread safe and therefore, you should only use async and await tasks from inside the UnitySynchronizationContext. Async tasks often allocate objects when invoked, which might cause performance issues if you overuse them.
Unity overwrites the default SynchronizationContext with a custom UnitySynchronizationContext and runs all the tasks on the main thread in both Edit and Play modes by default. To use async tasks, you must manually create and handle your own threads with the Task.Run API, and use the default SynchronizationContext instead of the Unity version.
Unity doesn’t automatically stop async tasks that run on managed threads when you exit Play mode. To listen for enter and exit Play mode events to stop the tasks manually, use EditorApplication.playModeStateChanged. If you take this approach, most of the Unity scripting APIs aren’t available to use unless you migrate the context back to the UnitySynchronizationContext.
In development buildsA development build includes debug symbols and enables the Profiler. More info
See in Glossary, Unity displays the following error message if you try to use Unity APIs in multithreaded code:
UnityException: Internal_CreateGameObject can only be called from the main thread. \
Constructors and field initializers will be executed from the loading thread when loading a scene. \
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
For performance reasons, Unity doesn’t perform checks for multithreaded behavior in non-development builds and doesn’t display this error in live builds. This means that while Unity doesn’t prevent execution of multithreaded code on live builds, random crashes and other unpredictable errors are likely if you do use multiple threads.
For this reason, you shouldn’t use your own multithreading and instead use Unity’s job system. The job system uses multiple threads safely to execute jobs in parallel and achieve the performance benefits of multithreading. For more information, see Job system overview.