- Home /
How do I expose class properties (not fields) in C#?
Is it possible to expose properties (as opposed to fields) of a custom class to the Unity editor?
The fields of a custom class can be exposed to the editor by adding a System.Serializable attribute (this is implicit in JavaScript, but not so in C#). This is documented here. However, I have noticed that this only works for fields, but not properties.
For example:
using UnityEngine; using System.Collections;
public class NewBehaviourScript : MonoBehaviour { public Test test = new Test(); }
[System.Serializable] public class Test { public int publicField = 5; private int _privateProperty = 0; public int publicProperty { get { return _privateProperty; } set { _privateProperty = value; } } }
In this example, publicField will be visible and editable to the Unity editor. Even _privateProperty will be visible to the editor (just not editable). However, publicProperty will not be accessible at all.
Is there any way to make properties exposed to the editor?
Answer by Ricardo · Apr 13, 2010 at 07:02 AM
You may want to write a custom editor. Unity just can't safely blanket serialize/deserialize properties, since getters and setters can have side effects.
That makes sense. Now I feel silly for not thinking of that. Thank you.
Heh, don't sweat it. Notice that I said blanket. They could, for instance, have attributes to indicate that a property getter can be safely called, or that a private member should be called ins$$anonymous$$d of a setter. Do vote for the request that Jessy linked to if you're interested on that sort of thing.
Answer by DenninDalke · Apr 27, 2012 at 01:51 AM
This work fine for me:
[SerializeField]
private int _SomeProperty;
public int SomeProperty {
get { return _SomeProperty; }
set { DoSomething(); }
}
Your setter will not get called when modified from the inspector, but I think it's not a big problem.
I've taken to using this solution in my code as well. I think its probably the nicest, simplest, solution if you don't like publicly exposing fields and prefer using properties to manage mutators.
If you are doing data binding via properties, that is a problem. I want my GameObject's name to change when I edit a property in the script but as my custom inspector can't work with properties, I have to check for any changes in the OnInspectorGUI and change the gameobject's name there, messy.
$$anonymous$$y setter is called a bunch when I modify the value in the inspector. What did you mean by, "Your setter will not get called when modified from the inspector"?
Actually, my setter is called when I just click on the inspector, and when I actually change a value through a property, it is called like 8 times.
Brushing off the cobwebs here and this answer was so helpful. Should be updated as best answer.
Answer by vexe · Sep 05, 2014 at 05:57 PM
Exposing properties was one of the very first things implemented in VFW, get it, it's free.
I also wrote in-depth answer here explaining how to go about serializing things including auto-properties.
ShowEmAll currently supports serializing interfaces, generics, auto-properties, dictionaries, abstracts, etc.
To expose a property, just annotate with ShowProperty
:
Answer by Jessy · Apr 13, 2010 at 04:58 AM
In addition, you can hide _privateProperty with [HideInInspector]
(link) if you want. Also, convention dictates that your properties should begin with a capital letter, if you care.
This feature request was already declined by Unity. The main reason i guess is that serialization / deserialization is mainly done from a seperate thread. Calling user properties would by tricky as it's not possible to proper synchronise the call. Also that would prevent the properties to use most of the Unity API which makes them even less useful.
You can use properties at runtime just fine. If you want validating code inside the editor, write an editor script / custom inspector / propertydrawer for your class / field.
Answer by sarahnorthway · Jun 01, 2018 at 02:40 AM
It can be done with a PropertyAttribute. An older version of Unity PostProcessing contained GetSetAttribute which calls a field's GetSet if it is changed via the inspector.
Put GetSetAttribute.cs in Assets/Scripts (not Editor):
using UnityEngine;
public sealed class GetSetAttribute : PropertyAttribute {
public readonly string name;
public bool dirty;
public GetSetAttribute(string name) {
this.name = name;
}
}
Put GetSetDrawer.cs in Assets/Scripts/Editor:
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(GetSetAttribute))]
sealed class GetSetDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
GetSetAttribute attribute = (GetSetAttribute)base.attribute;
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck()) {
attribute.dirty = true;
} else if (attribute.dirty) {
var parent = GetParentObject(property.propertyPath, property.serializedObject.targetObject);
var type = parent.GetType();
var info = type.GetProperty(attribute.name);
if (info == null)
Debug.LogError("Invalid property name \"" + attribute.name + "\"");
else
info.SetValue(parent, fieldInfo.GetValue(parent), null);
attribute.dirty = false;
}
}
public static object GetParentObject(string path, object obj) {
var fields = path.Split('.');
if (fields.Length == 1)
return obj;
FieldInfo info = obj.GetType().GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
obj = info.GetValue(obj);
return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj);
}
}
Use it like this:
using UnityEngine;
using UnityEngine.UI;
public class MyComponent : MonoBehaviour {
[SerializeField, GetSet("fillAmount")]
private float _fillAmount = 0.5f;
public float fillAmount {
get {
return _fillAmount;
}
set {
Debug.Log("Calling set!");
_fillAmount = value;
fillImage.fillAmount = value;
}
}
}
Thank you! It's a great solution, just what i was looking for tesing my float scriptable variables which had custom setters
[Edit: This code wont work. For a list, the path includes Array.data[x]. Now "data[x]" is the field I am trying to access by parsing out the "x" and getting the xth element of the Array. But the na$$anonymous$$g of the data elements of the array don't necessarily correspond to their order in the array, as I've just discovered, so this wont work. Does anyone have any idea how to use the path to deter$$anonymous$$e what element of the array I should access? ]
I wanted to have lists of objects with exposed getters and setters, but the GetParentObject subroutine was failing on those cases, so I hacked together an addition which seems to solve that problem. I am putting it here with no guarantee on its correctness. public static object GetParentObject(string path, object obj) { var fields = path.Split ('.'); var type = obj.GetType ();
if (fields.Length == 1){
return obj;
}
if (isList(obj)) {
string dataEntry = fields [1];
var delimiters = new Char[] { '[', ']' };
var parts = dataEntry.Split (delimiters);
int index = 0;
if (Int32.TryParse (parts [1], out index)) {
FieldInfo[] tempfields = type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in tempfields) {
if (field.FieldType.IsArray) {
Array array = field.GetValue (obj) as Array;
object listEntry = array.GetValue (index);
return GetParentObject (string.Join (".", fields, 2, fields.Length - 2), listEntry);
}
}
}
}
FieldInfo info = obj.GetType().GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
obj = info.GetValue(obj);
return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj);
}
private static bool isList(object val){
return (val.GetType ().IsGenericType && val.GetType().GetGenericTypeDefinition () == typeof(List<>));
}
I think I got this to work with arrays but I haven't tested it with lists. $$anonymous$$aybe my changes will help you in the right direction though if it doesn't work for lists.
using System.Reflection;
using UnityEditor;
using UnityEngine;
using System;
[CustomPropertyDrawer(typeof(GetAndSetAttribute))]
sealed class GetSetDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GetAndSetAttribute attribute = (GetAndSetAttribute)base.attribute;
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck())
{
attribute.dirty = true;
}
else if (attribute.dirty)
{
object parent = GetParentObject(property.propertyPath, property.serializedObject.targetObject);
Type type = parent.GetType();
Type arrayType = type.IsArray ? type.GetElementType() : null;
PropertyInfo info = arrayType == null ? type.GetProperty(attribute.name) : arrayType.GetProperty(attribute.name);
if (info == null)
{
Debug.LogError("Invalid property name \"" + attribute.name + "\"");
}
else
{
if(arrayType == null)
{
info.SetValue(parent, fieldInfo.GetValue(parent), null);
}
else
{
for (int i = 0; i < ((Array)parent).Length; i++)
{
object newParent = ((Array)parent).GetValue(i);
info.SetValue(newParent, fieldInfo.GetValue(newParent), null);
}
}
}
attribute.dirty = false;
}
}
public static object GetParentObject(string path, object obj)
{
string[] fields = path.Split('.');
if (fields.Length == 1)
return obj;
FieldInfo info = obj.GetType().GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (info != null) { obj = info.GetValue(obj); }
return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj);
}
}
First of all you didn't write this code! It was taken from here link text so give credit next time to the writer of the code!
Second thing this won't work for lists not even with the fix posted above.
Didn't she give credit for that code to "An older version of Unity PostProcessing"? That's probably the common link between her code and your link.
Your answer
Follow this Question
Related Questions
Can't find serialized field in Animation Window 1 Answer
How to expose a field of type Interface in the inspector? 10 Answers
Material doesn't have a color property '_Color' 4 Answers
expose public property (not variable) in inspector 9 Answers
Doesn't the C# JIT inline property getters in Unity? 3 Answers