Use serialized properties over serialized fields in Unity
Using serialized properties could be beneficial over serialized fields in Unity for your game: Easily change access modifiers and execute function, e.g. to update your UI.
In Unity, you normally use serialized fields if you want to expose something in the inspector:
public class Foo : MonoBehaviour
{
[SerializeField]
private string Bar;
}
Recently, in my open source tower defence game, I've started using serialized properties instead:
public class Foo : MonoBehaviour
{
[field: SerializeField]
private string Bar { get; set; }
}
It's a bit more to write for each serialized property. You can use live templates in your favorite IDE to reduce typing the boilerplate every time.
The field
attribute target targets the auto-generated backing field of the Bar
property. Since Unity can not serialize properties but fields, this simply instructs Unity to serialize the backing field (as you'll see in the next example).
Here are two reasons why I prefer this approach over the default one.
#1: Easily change access modifiers
In the default example, if you want to make Bar
public, you can do the following:
-private string Bar;
+public string Bar;
However, allows any other script to write to Bar
. To improve that, you're going to introduce a property as well:
[SerializeField]
private string _bar; // backing field
public string Bar { get => _bar; private set => _bar = value }
It's possible, but it has a major draw back: You will lose any assignment done to Bar
before, because the name has changed. For major refactoring this is a no-no for me. You could use [FormerlySerializedAs]
to help here, if you want.
If you use a serialized property, the change is as simple as that:
[field: SerializeField]
public string Bar { get; private set; }
No assignment will change, no [FormerlySerializedAs]
is needed, and you have a public getter and private setter.
If you want to make it writeable, in both cases, you can remove the private
before the set
.
At least for my development flow, changing access modifiers happens often, especially after prototyping systems and then refactoring them to better code. This has saved me tons of time.
#2: Execute functions
In a lot of scripts you can observe the following pattern:
[SerializeField]
private string Bar;
public string GetBar()
{
return Bar;
}
public void SetBar(string value)
{
// Some stuff done with bar before assigning it to the field.
Bar = value + "123";
// Or call another function to update something:
UpdateBarUi();
}
Both is just a verbose writing of getter and setter functions in a property. In some cases it gets super wild if the field Bar
is public
. Then you must determine if you want to set the field directly or use the access methods.
Unfortunately, this happens quite a lot in Unity APIs. For example you can useRigidbody.position
andRigidbody.MovePosition
. If you're not familiar with the API you may think the outcome is the same, but that's not the case. The first is teleporting the rigidbody and the second creates a smooth transition.
In the case where we need to save the value of Bar
we still need a backing field:
private string _bar; // backing field
[field: SerializeField]
public string Bar
{
get => _bar;
set
{
_bar = value + "123";
UpdateBarUi();
}
}
In terms of usage, its simply accessing Bar
for getting and setting instead of calling some functions. Reduces the API surface and reduces possible misunderstandings.
As with any property, don't do expensive function calls in their getter and setter!
IDE Live Template
You can add a live template for creating serialized properties using JetBrains Rider.
Here's my template if you want to use it:
[field: SerializeField]
private $TYPE$ $VAR$ { get; set; } = default!;
Drawback of using serialized property
There is one drawback when using serialized properties that also easy to solve. If you write custom editors and you're using SerializedObject
you're like also using its FindProperty
method to find serialized properties fields.
However, this method will no longer find a serialized property because the name of the serialized field is different from the name of a serialized field.
Using our Bar
example, Unity will serialize it with the name Bar
. However, for properties, Unity will serialize it with the name <Bar>k__BackingField
.
With a little extension method this drawback is easily solvable:
public static class SerializedObjectExtensions
{
/// <summary>
/// Finds a <see cref="SerializedProperty"/> C# property with a [field: SerializedField] attribute,
/// because Unity's <see cref="SerializedObject.FindProperty"/> does not work with C# properties.
/// </summary>
public static SerializedProperty FindRealProperty(this SerializedObject serializedObject, string name)
{
// This is the serialized name of a C# property.
var realName = $"<{name}>k__BackingField";
return serializedObject.FindProperty(realName);
}
}
Cheers!