Version: 2023.2
Unity 架构
.NET 配置文件支持

Unity 中的 .NET 概述

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 IL2CPP (Intermediate Language To C++), each of which uses a different compilation technique:

  • Mono 使用即时 (JIT) 编译,在运行时按需编译代码。
  • 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 backends.

托管代码剥离

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.

.NET 系统库

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 compilation 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.

使用第三方 .NET 库

应该只使用在各种 Unity 配置和平台上经过广泛测试的第三方 .NET 库。

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.

查看第三方库时,请考虑以下方面:

  • 兼容性:第三方库可能与某些 Unity 平台和脚本后端不兼容。
  • 性能:与其他 .NET 运行时相比,第三方库在 Unity 中可能具有截然不同的性能特征。
  • AOT 二进制文件大小:由于库使用的依赖项数量,第三方库可能会显著增大 AOT 二进制文件大小。

C# 反射开销

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.

要最大程度减少垃圾回收器开销,请在应用程序中避免使用诸如 Assembly.GetTypesType.GetMethods() 等方法,这些方法会在运行时创建许多 C# 反射对象。而是应该在编辑器中扫描程序集以获取所需数据,并进行序列化和/或代码生成以在运行时使用。

UnityEngine.Object 特殊行为

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 Camera 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# 和 Unity C++ 共享 UnityEngine 对象

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 builds, 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 架构
.NET 配置文件支持