Version: 2023.2
Language : English
Unity architecture
.NET profile support

Overview of .NET in Unity

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.

Scripting backends

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:

  • Mono uses just-in-time (JIT) compilation and compiles code on demand at runtime.
  • IL2CPP uses ahead-of-time (AOT) compilation and compiles your entire application before it runs.

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
.

Managed code stripping

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.

Garbage collection

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.

.NET system libraries

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 optimization method used by all platforms except iOS for optimizing the size of the built 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:

  • .NET Standard is a smaller API surface and so has a smaller implementation. This reduces the size of your final executable file.
  • .NET Standard has better cross-platform support, so your code is more likely to work across all platforms.
  • All .NET runtimes support .NET Standard, so your code works across more VM/runtime environments (for example, .NET Framework. .NET Core, Xamarin, Unity) when you use .NET Standard.
  • .NET Standard moves more errors to compile time. A number of APIs in .NET Framework are available at compile time, but have implementations on some platforms that throw an exception at runtime.

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.

Using third-party .NET libraries

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:

  • Compatibility: Third-party libraries might not be compatible with some Unity platforms and scripting backends.
  • Performance: Third-party libraries might have vastly different performance characteristics in Unity compared to other .NET runtimes.
  • AOT binary size: Third-party libraries might increase AOT binary size significantly because of the number of dependencies the library uses.

C# reflection overhead

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 special behavior

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.

Unity C# and Unity C++ share UnityEngine Objects

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 MonoBehaviours and ScriptableObjects: 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.

Async, tasks and Awaitable

Unity provides built-in Await support with specifically optimized async/await compatible types. In most cases, you should prefer using Awaitable over .net Task when creating an async method.

Most of the Unity API isn’t thread safe and therefore, you should only use Unity APIs from the main thread. Unity overwrites the default SynchronizationContext with a custom UnitySynchronizationContext and runs all the .net Tasks continuations on the main thread in both Edit and Play modes by default. If you explicitly want to run some code in a background thread (for heavy compute operations), you can do that by awaiting Awaitable.BackgroundThreadAsync as in the following example:

private async Awaitable<float> DoSomeHeavyComputationInBackgroundAsync(bool continueOnMainThread = true)
{
    await Awaitable.BackgroundThreadAsync();
    // do some heavy math here
    float result = 42;

    // by default, switch back to main thread:
    if(continueOnMainThread){
        await Awaitable.MainThreadAsync();
    }
    return result;
}

public async Awaitable Start()
{
    var computationResult = await DoSomeHeavyComputationInBackgroundAsync();
}

Unity doesn’t automatically stop code runnining in the background when you exit Play mode. To cancel a background operation on exiting playmode, use Application.exitCancellationToken.

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. Thus when writing code potentially running in a background thread, you need to be extra-careful at the APIs you are calling.

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.

Using Awaitable.BackgroundThreadAsync and getting back on main thread with Awaitable.MainThreadAsync is suitable for relatively long running background operations (e.g. longer than a frame), to avoid blocking the main game loop, but is not suitable for taking advantage of multi-core CPUs within a single frame.

Unity architecture
.NET profile support