The Unity.Collections namespace provides unmanaged C# containers that you can use in jobs and Burst-compiled code. The collections in this namespace fall into the following categories:
Unity.Collections namespace and their names start with Native.Unity.Collections.LowLevel.Unsafe namespace and their names start with Unsafe.The remaining collection types that don’t fit into these categories aren’t allocated and don’t contain any pointers. These types only contain small amounts of data, and their disposal and thread safety aren’t a concern.
Native collection types carry Unity’s AtomicSafetyHandle, which performs validation for access, disposal, and job dependencies. The Unsafe equivalents don’t carry these safety handles. These safety handles are separate from index bounds checking, and several Unsafe types, such as UnsafeList and UnsafePtrList, bounds-check their indexers via CollectionHelper.CheckIndexInRange. This works in the same way as the equivalent conditional checks in Native types.
Checks are always enabled in the Editor, and in Player builds with the Checked managed code variant. The only way to make these checks fully zero-cost is to run in a Player build that doesn’t include safety checks.
It’s important to understand that disabling safety checks in the Editor doesn’t compile out these checks:
Neither of these options reduces the cost of checks overall to zero.
It’s best practice to use Native collections over the Unsafe equivalents, because the safety handles catch access, disposal, and job-dependency errors. However, it’s sometimes necessary to use the Unsafe equivalents, because Native collection types can’t contain other Native collections. This limitation comes from how Unity implements the Native safety handles. For example, if you want a list of lists, you can use either NativeList<UnsafeList<T>> or UnsafeList<UnsafeList<T>>, but you can’t use NativeList<NativeList<T>>.
Most Native collections are implemented as wrappers of their Unsafe counterparts. For example, NativeList is made up of an UnsafeList plus the handles that the safety checks use. Because of this wrapping, a Native type is not free even when checks are compiled out: the internal Unsafe collection introduces an extra pointer indirection, which adds cost by itself even in unchecked Player builds.
Several Native types have Unsafe equivalents, for example, NativeList has UnsafeList, and NativeHashMap has UnsafeHashMap.
The following table summarizes the comparative safety features of Native and Unsafe collection types:
| Behavior | Native | Unsafe |
|---|---|---|
| Job safety checks | Yes | No |
| Index bound checks | Yes | Partially (when safety checks are enabled) |
| Containing behavior | Can only contain Unsafe collections |
Can contain either |
The way changes are reflected when you pass a collection by value differs between collection types:
NativeList by value and either write into the data or resize the list, the changes are reflected in the caller’s copy of the list.UnsafeList by value and write into the data, those writes are reflected in the caller’s version. However, if you resize the list, that change is not reflected in the caller’s copy, and moreover all subsequent writes are not reflected either.FixedList by value, nothing is reflected in the caller’s copy, because the entire contents are copied when you pass it.