Serialization rules analyzer reference
The Unity serialization rules analyzer helps you write correct serialization code by detecting common mistakes at compile time.
Read time 20 minutesLast updated 13 days ago
Unity has a serialization Roslyn diagnostic analyzer that helps you write correct serialization code by detecting common mistakes in using Unity's serialization system at compile time. It provides real-time feedback in your IDE to prevent serialization issues before they cause runtime problems.
For detailed information about Unity's serialization rules, refer to Script Serialization Rules.
Analyzer rules
The serialization analyzer implements the following diagnostic rules:
- UAC1000: SerializeReference field type missing the Serializable attribute
- UAC1001: Public field skipped by serialization due to missing the Serializable attribute
- UAC1002: Inheritance hierarchy incomplete serialization
- UAC1003: [SerializeReference] used on struct
- UAC1004: [SerializeReference] used on primitive or enum type
- UAC1005: Direct self-reference cycle in serialized class
- UAC1006: Field uses type with direct self-reference cycle
- UAC1007: Deep self-reference cycle in serialized class
- UAC1008: Field uses type with deep self-reference cycle
- UAC1009: Unsupported collection type for serialization
- UAC1010: [SerializeField] used on non-serializable type
- UAC1011: Enum type exceeds 32-bit size limit
For information about suppressing rule warnings, refer to Suppressing warnings.
UAC1000: SerializeReference field type missing the Serializable attribute
Severity: Warning.
When a field is serialized by , its type should have the attribute. If the type is missing , Unity issues a warning. The field is serialized via even though the type isn't considered serializable in other serialization contexts, which can lead to inconsistent behavior. To avoid the issue, add to the class or use the attribute.
[SerializeReference][Serializable][Serializable][SerializeReference][Serializable][MakeSerializable]Marking the type with (or when you can't modify the type) makes the intent explicit and keeps behavior consistent across Unity's serialization paths. Without it, a field with still persists the data, while other forms of serialization that rely on the type being serializable can skip it, leading to inconsistent results.
[Serializable][MakeSerializable][SerializeReference]Incorrect example: Missing [Serializable] on a class used with [SerializeReference]:
// INCORRECT: Missing [Serializable] on CustomDatapublic class CustomData{ public int value;}public class MyBehaviour : MonoBehaviour{ [SerializeReference] public CustomData data; // UAC1000: CustomData lacks [Serializable]}
Correct example: Add [Serializable] to the class:
// CORRECT: Add [Serializable] to the class[System.Serializable]public class CustomData{ public int value;}public class MyBehaviour : MonoBehaviour{ [SerializeReference] public CustomData data; // OK}
UAC1001: Public field skipped by serialization due to missing the Serializable attribute
Severity: Warning.
Unity considers public fields of custom classes as candidates for serialization. Without the attribute on a type, Unity does not serialize fields of this type, and you can lose data when entering Play mode or when the scene is saved.
[Serializable]To fix this issue, you have multiple options depending on your intent:
- Add to the field’s type (if serialization is needed).
[Serializable] - Make the field private (if serialization isn't needed).
- Add to the field (to explicitly mark it as non-serialized).
[System.NonSerialized] - Convert the field into a property (Unity doesn't serialize properties), if you only need a public API surface.
Without the attribute, Unity skips the field during serialization, causing data loss. This is problematic because the field compiles successfully but loses its value during serialization and deserialization cycles.
[Serializable]Incorrect example: Missing [Serializable] on a class used as a serialized field:
// INCORRECT: Missing [Serializable] on PlayerStatspublic class PlayerStats{ public int health; public int mana;}public class Player : MonoBehaviour{ public PlayerStats stats; // UAC1001: PlayerStats lacks [Serializable]}
Correct example: Add [Serializable] to the class:
// CORRECT: Add [Serializable] to the class[System.Serializable]public class PlayerStats{ public int health; public int mana;}public class Player : MonoBehaviour{ public PlayerStats stats; // OK - will be serialized inline}
UAC1002: Inheritance hierarchy incomplete serialization
Severity: Warning.
Every class in a serialization hierarchy must be explicitly marked with , because the attribute isn't inherited from a base class to its derived classes. If any class in the hierarchy is missing , Unity might not serialize the fields declared on that class, which can result in unexpected data loss when entering Play mode, saving scenes, or performing domain reloads.
[Serializable][Serializable]If you can't modify a base class (for example, it comes from a third-party library or a precompiled assembly), use the assembly-level attribute to mark it as serializable without changing its source code. Fixing this warning also enables the analyzer to check the base class's own fields. Refer to Limitation: base class field analysis for more details.
[MakeSerializable]Incorrect example: Inheritance hierarchy missing [Serializable] on a base class:
// INCORRECT: Missing [Serializable] on base classpublic class BaseEntity{ public string name;}[System.Serializable]public class Enemy : BaseEntity // UAC1002: Enemy's hierarchy incomplete (BaseEntity lacks [Serializable]){ public int damage;}
Correct example: Mark each class in the hierarchy with [Serializable]:
// CORRECT: Mark each class in the hierarchy with [Serializable][System.Serializable]public class BaseEntity{ public string name;}[System.Serializable]public class Enemy : BaseEntity{ public int damage;}public class GameManager : MonoBehaviour{ public Enemy enemy; // OK - entire hierarchy is serializable}
Alternative correct example: Use [MakeSerializable] for inaccessible base classes:
If you can't modify the base class (for example, it comes from a precompiled assembly or third-party library), you can use the assembly attribute:
[MakeSerializable]// CORRECT: Use [MakeSerializable] for inaccessible base classes[assembly: UnityEngine.Serialization.MakeSerializable(typeof(ThirdPartyLibrary.BaseEntity))]namespace ThirdPartyLibrary{ // This class is from a precompiled assembly - you can't modify it public class BaseEntity { public string name; }}namespace YourGame{ [System.Serializable] public class Enemy : ThirdPartyLibrary.BaseEntity { public int damage; } public class GameManager : MonoBehaviour { public Enemy enemy; // OK - BaseEntity marked serializable via [MakeSerializable] }}
UAC1003: [SerializeReference] used on struct
Severity: Error.
The attribute can only be used on reference types (classes). It enables reference semantics such as null support, polymorphism, and shared references, which are incompatible with value types. Structs are value types and are always serialized inline, so using on a struct causes a runtime error.
[SerializeReference][SerializeReference]Incorrect example: Using [SerializeReference] on a struct:
// INCORRECT: Using [SerializeReference] on a struct[System.Serializable]public struct Vector3D{ public float x, y, z;}public class MyBehaviour : MonoBehaviour{ [SerializeReference] public Vector3D position; // UAC1003: Error - can't use [SerializeReference] on struct}
Correct example: Remove [SerializeReference] from the struct field:
// CORRECT: Remove [SerializeReference] from the struct field[System.Serializable]public struct Vector3D{ public float x, y, z;}public class MyBehaviour : MonoBehaviour{ public Vector3D position; // OK - structs are always serialized inline}
Alternative correct example: Use a class:
// CORRECT: Use a class instead of a struct[System.Serializable]public class Vector3D{ public float x, y, z;}public class MyBehaviour : MonoBehaviour{ [SerializeReference] public Vector3D position; // OK - class can use [SerializeReference]}
UAC1004: [SerializeReference] used on primitive or enum type
Severity: Warning.
Built-in types (, , , etc.) and enums are value types, so they are always serialized inline. Unity silently ignores on these types instead of producing a runtime error. The behavior is different for structs, where Unity raises errors which are caught by UAC1003). Removing the unnecessary makes the serialization intent of the field clearer.
intfloatbool[SerializeReference][SerializeReference]Incorrect example: Using [SerializeReference] on a primitive or enum:
// INCORRECT: Using [SerializeReference] on a primitive and an enumpublic enum GameState{ Menu, Playing, Paused}public class GameController : MonoBehaviour{ [SerializeReference] public int score; // UAC1004: int is a primitive type [SerializeReference] public GameState state; // UAC1004: GameState is an enum}
Correct example: Remove [SerializeReference] from primitive and enum fields:
// CORRECT: Remove [SerializeReference] from primitive and enum fieldspublic enum GameState{ Menu, Playing, Paused}public class GameController : MonoBehaviour{ public int score; // OK - primitives don't need [SerializeReference] public GameState state; // OK - enums don't need [SerializeReference]}
UAC1005: Direct self-reference cycle in serialized class
Severity: Warning.
When a class contains a field of its own type, it creates a direct self-reference cycle. Unity's inline serialization copies object graphs by value, which can't represent cycles. To avoid infinite loops, Unity ignores any field of a type that contains a direct self-reference cycle. This causes data loss that can be difficult to debug, because the field compiles without errors but is always null after deserialization. UAC1006 complements this rule by reporting the same problem at the field's usage location rather than the type declaration.
There are two common, valid ways to represent self-reference data:
- Use on the link field (supports cycles and reference semantics).
[SerializeReference] - Use a collection (for example, or
List<Node>) to represent children or links. Collections can be empty and the object graph can remain cycle-free, but cycles (including addingNode[]) still can't be represented correctly without thethisattribute.[SerializeReference]
Incorrect example: Direct self-reference cycle in an inline-serialized class:
// INCORRECT: Direct self-reference cycle[System.Serializable]public class Node{ public string data; public Node next; // UAC1005: Direct self-reference - Unity will ignore this field}public class LinkedList : MonoBehaviour{ public Node head; // After serialization, head will always be null}
Correct example: Use [SerializeReference] for self-referential links:
// CORRECT: Use [SerializeReference] for self-referential links[System.Serializable]public class Node{ public string data; [SerializeReference] public Node next; // OK - [SerializeReference] supports cycles}public class LinkedList : MonoBehaviour{ [SerializeReference] public Node head; // OK - preserves linked structure}
Alternative correct example: Redesign to avoid cycles:
// CORRECT: Redesign to avoid cycle[System.Serializable]public class NodeData{ public string data; public int nextIndex; // Store index instead of direct reference}public class LinkedList : MonoBehaviour{ public NodeData[] nodes; // OK - no direct cycles}
UAC1006: Field uses type with direct self-reference cycle
Severity: Warning.
When you use a type that contains a direct self-reference cycle, Unity ignores that field during serialization. This warning appears at the usage location so you can notice data loss even when the problematic type is declared elsewhere. Refer to UAC1005 for details on what causes a direct self-reference cycle and how to fix it.
Incorrect example: Field uses a type with a direct self-reference cycle:
// INCORRECT: Field uses type with direct self-reference cycle[System.Serializable]public class TreeNode{ public string value; public TreeNode left; // Direct self-reference public TreeNode right; // Direct self-reference}public class Tree : MonoBehaviour{ public TreeNode root; // UAC1006: TreeNode contains self-reference cycles // After serialization, the root field will be ignored and remain null}
Correct example: Use [SerializeReference] for cyclic references:
// CORRECT: Use [SerializeReference] for cyclic references[System.Serializable]public class TreeNode{ public string value; [SerializeReference] public TreeNode left; // Fixed with [SerializeReference] [SerializeReference] public TreeNode right; // Fixed with [SerializeReference]}public class Tree : MonoBehaviour{ [SerializeReference] public TreeNode root; // OK - preserves tree structure}
UAC1007: Deep self-reference cycle in serialized class
Severity: Warning.
When a class contains fields that create an indirect cycle (A > B > A or A > B > C > A), Unity still serializes the structure, but limits recursion to 10 levels. This is different from direct cycles, which are ignored entirely. After 10 levels Unity stops and discards the remaining data, which can cause silent data loss in deeply nested trees or graphs. This warning is reported on the field that participates in the cycle. UAC1008 reports the same problem at the usage location.
Incorrect example: Deep self-reference cycle with inline serialization:
// INCORRECT: Deep self-reference cycle with inline serialization[System.Serializable]public class Parent{ public string name; public Child child; // UAC1007: Creates deep cycle (Parent → Child → Parent)}[System.Serializable]public class Child{ public string name; public Parent parent; // Creates the cycle back to Parent}public class Family : MonoBehaviour{ public Parent root; // Will serialize up to 10 levels: Parent→Child→Parent→Child...}
Correct example: Use [SerializeReference] for deep cycles:
// CORRECT: Use [SerializeReference] for deep cycles[System.Serializable]public class Parent{ public string name; [SerializeReference] public Child child; // OK - [SerializeReference] handles cycles}[System.Serializable]public class Child{ public string name; [SerializeReference] public Parent parent; // OK - preserves bidirectional relationship}public class Family : MonoBehaviour{ [SerializeReference] public Parent root; // OK - no depth limit}
Alternative correct example: Redesign to avoid cycles:
// CORRECT: Redesign to avoid cycles[System.Serializable]public class Parent{ public string name; public List<string> childNames; // Store references differently}[System.Serializable]public class Child{ public string name; public string parentName; // Use name lookup instead of direct reference}public class Family : MonoBehaviour{ public List<Parent> parents; // OK - no cycles public List<Child> children; // OK - no cycles}
UAC1008: Field uses type with deep self-reference cycle
Severity: Warning.
When you use a type that contains a deep self-reference cycle, Unity serializes it with a 10-level recursion limit, which can cause silent data loss in deeply nested instances. This warning appears at the usage location so you can catch the issue even when the problematic type is declared elsewhere. Refer to UAC1007 for details on what causes a deep cycle and how to fix it.
Incorrect example: Field uses a type with a deep self-reference cycle:
// INCORRECT: Field uses type with deep self-reference cycle[System.Serializable]public class A{ public B b; // Creates deep cycle: A -> B -> A}[System.Serializable]public class B{ public A a; // Completes the cycle}public class Host : MonoBehaviour{ public A root; // UAC1008: A contains a deep self-reference cycle}
Correct example: Use [SerializeReference] to avoid depth limits:
// CORRECT: Use [SerializeReference] to avoid depth limits[System.Serializable]public class GraphNode{ public string id; [SerializeReference] public List<GraphNode> connections; // OK - no depth limit}public class Graph : MonoBehaviour{ [SerializeReference] public List<GraphNode> nodes; // OK - handles arbitrary depth}
Alternative correct example: Use IDs:
// CORRECT: Use IDs instead of direct references[System.Serializable]public class GraphNode{ public string id; public List<string> connectionIds; // Store IDs instead of references}public class Graph : MonoBehaviour{ public List<GraphNode> nodes; // OK - no cycles, resolve IDs at runtime public GraphNode FindNode(string id) { return nodes.Find(n => n.id == id); }}
UAC1009: Unsupported collection type for serialization
Severity: Warning.
Unity's serialization system only supports two collection types: single-dimensional arrays () and . Fields that use unsupported collection types aren't serialized, which can cause data loss because the field compiles and works at runtime but loses its value after a serialization cycle (for example, when saving a scene or reloading the domain).
T[]List<T>Unsupported collection types include:
- Multidimensional arrays (for example, ,
int[,]).int[,,] - Jagged arrays (for example, ).
int[][] - Nested lists (for example, ).
List<List<T>> - ,
Dictionary<TKey, TValue>,HashSet<T>,Queue<T>, and other collections fromStack<T>.System.Collections.Generic
Incorrect example: Using unsupported collection types for serialization:
// INCORRECT: Using unsupported collection typespublic class InventorySystem : MonoBehaviour{ public int[,] grid; // UAC1009: Multidimensional array not supported public int[][] jaggedArray; // UAC1009: Jagged array not supported public Dictionary<string, int> itemCounts; // UAC1009: Dictionary not supported public HashSet<string> unlockedAchievements; // UAC1009: HashSet not supported public Queue<string> messageQueue; // UAC1009: Queue not supported public Stack<string> undoStack; // UAC1009: Stack not supported}
Correct example: Mark runtime-only collections as [NonSerialized]:
If you don't need these fields to persist (for example, they are runtime-only data structures), you can either make them non-serialized (for example by making the field non-public) or mark them with if they must remain a public field. This acknowledges they won't be serialized and suppresses the warning:
[System.NonSerialized]// CORRECT: Mark runtime-only collections as [NonSerialized]public class InventorySystem : MonoBehaviour{ [System.NonSerialized] public Dictionary<string, int> itemCounts; // OK - explicitly marked as non-serialized [System.NonSerialized] public HashSet<string> unlockedAchievements; // OK - runtime-only data [System.NonSerialized] public Queue<string> messageQueue; // OK - transient queue [System.NonSerialized] public Stack<string> undoStack; // OK - runtime undo stack private void Awake() { // Initialize runtime collections itemCounts = new Dictionary<string, int>(); unlockedAchievements = new HashSet<string>(); messageQueue = new Queue<string>(); undoStack = new Stack<string>(); }}
Alternative correct example: Use supported collection types:
If you need the data to persist, convert to supported collection types:
// CORRECT: Use supported collection types[System.Serializable]public struct KeyValuePair{ public string key; public int value;}public class InventorySystem : MonoBehaviour{ public List<KeyValuePair> itemCounts; // OK - List is supported public List<string> unlockedAchievements; // OK - List instead of HashSet public List<string> messageQueue; // OK - List instead of Queue public List<string> undoStack; // OK - List instead of Stack // Helper methods to maintain collection semantics public int GetItemCount(string itemName) { var pair = itemCounts.Find(p => p.key == itemName); return pair.value; } public void SetItemCount(string itemName, int count) { int index = itemCounts.FindIndex(p => p.key == itemName); if (index >= 0) itemCounts[index] = new KeyValuePair { key = itemName, value = count }; else itemCounts.Add(new KeyValuePair { key = itemName, value = count }); }}
Alternative correct example: Use a custom serializable wrapper with ISerializationCallbackReceiver:
If you need the full semantics of a or while also keeping the data serialized, you can create a wrapper class that implements . This allows you to store data in a Unity-supported format (arrays or ) and convert to and from the runtime collection in the serialization callbacks:
HashSetDictionaryISerializationCallbackReceiverList<T>// CORRECT: Use a custom serializable wrapper[System.Serializable]public class SerializableHashSet<T> : ISerializationCallbackReceiver{ [SerializeField] private List<T> items = new List<T>(); private HashSet<T> hashSet; public void OnBeforeSerialize() { if (hashSet == null) hashSet = new HashSet<T>(items); items.Clear(); items.AddRange(hashSet); } public void OnAfterDeserialize() { hashSet = new HashSet<T>(items); } // Add collection methods (Add, Remove, Contains, Count, etc.) as needed.}public class AchievementManager : MonoBehaviour{ public SerializableHashSet<string> unlockedAchievements; // OK - custom serializable wrapper}
The same pattern works for using parallel key-value lists. For complete, ready-to-use implementations of and , refer to Custom serialization.
Dictionary<TKey, TValue>SerializableHashSet<T>SerializableDictionary<TKey, TValue>UAC1010: [SerializeField] used on non-serializable type
Severity: Warning.
Applying to a field does not make its type serializable. The attribute instructs Unity to save the field, but if the field's type is not serializable, Unity does not save the value and the field resets after a serialization cycle (for example, when reloading a scene, reloading the domain, or reopening the project).
[SerializeField]To fix this warning, choose the option that matches your intent:
- If serialization is needed, make the field's type serializable. For example, add , or use
[Serializable]if you can't modify the type.[MakeSerializable] - If serialization is not needed, remove , or make the field non-serialized in another way.
[SerializeField] - If the field must remain public but should not be serialized, add to make that explicit.
[System.NonSerialized]
Incorrect example: Using [SerializeField] on a non-serializable type:
// INCORRECT: Using [SerializeField] on a non-serializable typepublic class MyWindowState : BaseState{ public List<int> expandedItems;}public class MyWindowManager : ScriptableObject{ [SerializeField] private MyWindowState m_WindowState; // UAC1010: Unity won't persist this field}
Correct example: Remove [SerializeField] when persistence isn't needed:
If the field doesn't need to be serialized (for example, it's runtime-only state), remove :
[SerializeField]// CORRECT: Remove [SerializeField] when persistence isn't neededpublic class MyWindowState : BaseState{ public List<int> expandedItems;}public class MyWindowManager : ScriptableObject{ private MyWindowState m_WindowState; // OK - not serialized}
Correct example: Mark the field as [NonSerialized] when persistence isn't needed:
If the field must remain public but shouldn't be serialized, add :
[System.NonSerialized]// CORRECT: Mark the field as [NonSerialized]public class MyWindowState : BaseState{ public List<int> expandedItems;}public class MyWindowManager : ScriptableObject{ [System.NonSerialized] public MyWindowState windowState; // OK - explicitly not serialized}
Correct example: Make the type [Serializable] when persistence is needed:
If you need the field to be serialized, make the type :
[Serializable]// CORRECT: Make the type [Serializable][System.Serializable]public class MyWindowState : BaseState{ public List<int> expandedItems;}public class MyWindowManager : ScriptableObject{ [SerializeField] private MyWindowState m_WindowState; // OK - type is now serializable}
UAC1011: Enum type exceeds 32-bit size limit
Severity: Warning.
Unity's serialization system only supports enum types that are 32 bits or smaller (, , , , , and ). Enums with underlying types of (64-bit signed) or (64-bit unsigned) exceed this limit and aren't serialized correctly, which causes data loss after a serialization cycle (for example, when saving a scene or reloading the domain).
bytesbyteshortushortintuintlongulongIncorrect example: Using a 64-bit enum for serialization:
// INCORRECT: Using a 64-bit enum for serializationpublic enum LargeEnum : long{ Value1 = 1L, Value2 = 2L, Value3 = long.MaxValue}public class GameData : MonoBehaviour{ public LargeEnum largeEnum; // UAC1011: 64-bit enum exceeds Unity's 32-bit limit}
Correct example: Use a 32-bit-or-smaller enum underlying type:
// CORRECT: Use a 32-bit-or-smaller enum underlying typepublic enum SmallEnum : int{ Value1 = 1, Value2 = 2, Value3 = int.MaxValue}public class GameData : MonoBehaviour{ public SmallEnum smallEnum; // OK - 32-bit enum is supported}
Correct example: Mark the enum field as non-serialized:
If you don't need the enum field to persist (for example, it's runtime-only state), either make it non-public or mark it with if it must remain a public field:
[System.NonSerialized]// CORRECT: Mark the enum field as non-serializedpublic enum LargeEnum : long{ Value1 = 1L, Value2 = 2L}public class GameData : MonoBehaviour{ [System.NonSerialized] public LargeEnum largeEnum; // OK - explicitly marked as non-serialized private void Awake() { // Initialize runtime enum value largeEnum = LargeEnum.Value1; }}
Suppressing warnings
If you have a valid reason to suppress a specific analyzer warning, you can use the directive:
#pragma warning disable#pragma warning disable UAC1001public class IntentionallyUnserializedClass{ public int value;}#pragma warning restore UAC1001
Suppressing these warnings is not recommended, because they indicate important serialization issues that can cause problems at runtime.
Limitation: base class field analysis
The analyzer only analyzes fields on types that are serialization roots (types marked with , types inheriting from , etc.). The analyzer doesn't analyze the fields of base classes that aren't serialization roots, even if they are part of an inheritance hierarchy with a serialization root. In the following example, triggers the UAC1002 warning because its base class lacks the attribute, but the fields on itself ( and ) don't trigger any warnings because isn't a serialization root.
[Serializable]UnityEngine.ObjectDerivedClassBaseClass[Serializable]BaseClasssettingsdataBaseClasspublic class BaseClass{ public Dictionary<string, int> settings; // No UAC1009 — BaseClass isn't a serialization root public CustomData data; // No UAC1001 — BaseClass isn't a serialization root}public class CustomData { }[System.Serializable]public class DerivedClass : BaseClass // UAC1002: BaseClass lacks [Serializable]{ public int value; // This field is analyzed}
To get warnings for the base class fields, mark the base class with the attribute so it becomes a serialization root:
[Serializable][System.Serializable]public class BaseClass{ public Dictionary<string, int> settings; // UAC1009: reported public CustomData data; // UAC1001: reported}public class CustomData { }[System.Serializable]public class DerivedClass : BaseClass{ public int value;}
Mark base classes with the attribute if you need their fields to be validated. This makes them serialization roots and enables the analyzer to check their fields.
[Serializable]Avoiding common serialization issues
Unity skips fields that don't follow its serialization rules, often without producing a visible error, which can cause unexpected data loss. The analyzer detects these issues at compile time. To avoid the most common problems, follow these practices:
-
Addto every custom class you intend to serialize.
[Serializable]This applies to both inline serialization and.[SerializeReference] -
Mark every class in an inheritance hierarchy with.
[Serializable]The attribute isn't inherited, so each class must be explicitly marked. -
Usefor complex relationships.
[SerializeReference]If your data structure involves null references, polymorphism, shared references, or cycles, useinstead of inline serialization.[SerializeReference] -
Avoid cycles in inline serialization.Without, direct self-reference cycles cause Unity to ignore the field entirely, and indirect cycles are serialized with a 10-level recursion limit. Use indices, IDs, or other indirect references to avoid cycles.
[SerializeReference] -
Don't useon value types.
[SerializeReference]Structs, primitives, and enums are always serialized inline.only works with reference types (classes).[SerializeReference]