Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

C# Compiler C# 7.3 [field: SerializeField] support

Discussion in 'Experimental Scripting Previews' started by Baste, Oct 25, 2018.

  1. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    C# 7.3 introduces the ability to target backing fields of properties with attributes. This allows us to do some very nice things, like this:

    Code (csharp):
    1. public class TestScript : MonoBehaviour {
    2.  
    3.     [field: SerializeField]
    4.     public int foo { get; private set; }
    This works, but it's not exactly an ideal look:

    upload_2018-10-25_15-23-50.png

    Is there any plans to give this proper support?

    Something that would have been even more powerful, and which doesn't exist at all now, is to have read-only properties with serialized data:

    Code (csharp):
    1. public class TestScript : MonoBehaviour {
    2.  
    3.     [field: SerializeField]
    4.     public int bar { get; }
    5. }
    I'm not quite sure how the internals of this works, but Unity doesn't seem to try to serialize the backing field. This syntax would allows us to encode that something should only be settable from the inspector, which isn't something we can do currently.

    This was brought up on the scripting forums, I figured I'd make a post here as it's more likely to reach the relevant people.
     
  2. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    It looks like it could make sense to properly support this.

    We would need to investigate exactly what the C# compiler emits for this new feature and see how well it fits with the Unity serialization system to know whether it is feasible to add support for without major changes.

    I've added it to our backlog to look into this and also linked this thread on the task.
     
  3. Kitsuba

    Kitsuba

    Joined:
    Mar 5, 2015
    Posts:
    17
    Let's hope it makes its way onto the list of planned features! I've been waiting a long while for auto properties to be serializable (without being dependent on the Odin Serializer asset)
     
    phobos2077 likes this.
  4. james7132

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    166
    Code (CSharp):
    1. [SerializeField] int _intField;
    2. public int IntProperty => _intField;
    I find this kind of boilerplate very often in runtime readonly data fields. It'd be great to have this feature to reduce the amount of noise when working with said code.
     
  5. IgnisIncendio

    IgnisIncendio

    Joined:
    Aug 16, 2017
    Posts:
    223
    I second this! This will make good C# code using properties so much easier.
     
  6. TheZombieKiller

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    265
    Currently you can play around with a PropertyDrawer to rename a field, but unfortunately I can't find a way to make this work with arrays (the PropertyDrawer only seems to receive the elements of the array, instead of the array itself, so it can't be renamed).

    Code (CSharp):
    1. class RenameFieldAttribute : PropertyAttribute
    2. {
    3.     public string Name { get; }
    4.  
    5.     public RenameFieldAttribute(string name) => Name = name;
    6.  
    7. #if UNITY_EDITOR
    8.     [CustomPropertyDrawer(typeof(RenameFieldAttribute))]
    9.     class FieldNameDrawer : PropertyDrawer
    10.     {
    11.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    12.         {
    13.             string[] path = property.propertyPath.Split('.');
    14.             bool isArray  = path.Length > 1 && path[1] == "Array";
    15.  
    16.             if (!isArray && attribute is RenameFieldAttribute fieldName)
    17.                     label.text = fieldName.Name;
    18.  
    19.             EditorGUI.PropertyField(position, property, label, true);
    20.         }
    21.     }
    22. #endif
    23. }
    Example:

    Code (CSharp):
    1. class TestBehaviour : MonoBehaviour
    2. {
    3.     [field: SerializeField]
    4.     [field: RenameField(nameof(MyProperty))]
    5.     int MyProperty { get; set; } = 10;
    6.  
    7.     void Start()
    8.     {
    9.         Debug.Log(MyProperty);
    10.     }
    11. }
    upload_2018-11-30_10-55-36.png
     
    Talmagett_Games likes this.
  7. IgnisIncendio

    IgnisIncendio

    Joined:
    Aug 16, 2017
    Posts:
    223
    Is it possible to make this work automatically with [field: SerializeField]?
     
  8. james7132

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    166
    Tangentially related: If the decision is to add in support for this, and it works on readonly properties, can we expand this to fields marked readonly as well? This should make it easier to declaratively state that the value is meant to be serialized, but explicitly disallowed to be modified at runtime.
     
  9. JakubSmaga

    JakubSmaga

    Joined:
    Aug 5, 2015
    Posts:
    417
    phobos2077 and rakkarage like this.
  10. bddckr

    bddckr

    Joined:
    Sep 13, 2016
    Posts:
    28
    I know this is currently only used for enum properties, but allowing to override the display name (without changing the (backing) field's name) via
    UnityEngine.InspectorNameAttribute
    would be great and solve this as well as potentially lots of other use cases. (This attribute could have just been EnumMemberAttribute instead btw...)

    Better handling by renaming backing fields automatically would still be preferred by me, though.
     
  11. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    This is still on our backlog and is currently not a high priority task.
     
  12. Artifact-Jesse

    Artifact-Jesse

    Joined:
    May 5, 2013
    Posts:
    5
    Oh man, add my vote to the pile... I've had my fingers crossed for this for quite literally years.
     
    phobos2077 likes this.
  13. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Just stumbled upon this when Rider suggested the new [field: ] syntax. But renaming the label in the Inspector is not really a solution, because when you look at the serialized asset, it uses the compiler-generated backing field name for property name as well, which is not a good idea to use like that (this is compiler-specific, backing field naming can change any time).

    IMO properly supporting auto-properties is the final piece of the puzzle in bringing Unity C# on the same level as "normal" C# development. Unity's serializer should understand auto-properties as it's own thing and serialize them using human-written property name along with the correct Label.
     
    a436t4ataf and Can-Baycay like this.
  14. DanielSuperKing

    DanielSuperKing

    Joined:
    Mar 26, 2019
    Posts:
    11
    I just want to add a little bump on this. I write so much boiler plate private backing field code... this would literally save me hundreds of lines of typing a week.

    Please please please please add this feature.
     
    reinfeldx and phobos2077 like this.
  15. DanielSuperKing

    DanielSuperKing

    Joined:
    Mar 26, 2019
    Posts:
    11
    I honestly want this MORE than any other possible Unity feature right now.
     
  16. andrej-szontagh

    andrej-szontagh

    Joined:
    May 18, 2017
    Posts:
    20
    How Unity Dev. team approach a task priority assignment ? There might be ton of "low priority" tasks that might have high impact/effort ratio because of low effort and simply never get solved because of low impact the priority is set low. Maybe it should be set based on the impact/effort ratio rather than just by impact itself. (It's just an random idea, I have no idea how this actually works inside Unity or if this is a good idea at all or if this problem is actually low effort or not)
     
    reinfeldx likes this.
  17. bddckr

    bddckr

    Joined:
    Sep 13, 2016
    Posts:
    28
    SugoiDev likes this.
  18. DanielSuperKing

    DanielSuperKing

    Joined:
    Mar 26, 2019
    Posts:
    11
    Oooooh, @bddckr I'm going to give this a try tonight.
     
  19. mellinoe

    mellinoe

    Joined:
    Aug 25, 2018
    Posts:
    1
    This would be extremely valuable and would allow us to remove a large amount of unavoidable boilerplate. It would also allow us to further eliminate some of our Unity-specific style guidelines.

    Detecting auto-generated backing fields should be trivial. Roslyn always attaches a
    System.Runtime.CompilerServices.CompilerGeneratedAttribute
    to them, and they have a distinctive name which is not expressible using any .NET language that I am aware of (e.g.
    <ExampleVariable>k__BackingField
    ).
     
    phobos2077 and DanielSuperKing like this.
  20. hurleybird

    hurleybird

    Joined:
    Mar 4, 2013
    Posts:
    258
    It should be! Between, like, everything planned for 2019.2 and this, I'd rather have this! It would effectively make every single thing I do a bit more convenient.
     
    reinfeldx, Stardog, Ryiah and 6 others like this.
  21. ZenTeapot

    ZenTeapot

    Joined:
    Oct 19, 2014
    Posts:
    65
    same. bump
     
    reinfeldx likes this.
  22. rerwandi

    rerwandi

    Joined:
    Dec 8, 2014
    Posts:
    544
    I always using [SerializedField] and write properties,
    Now that rider suggested me to use this auto-property, i also want this soon supported
     
  23. Vharz

    Vharz

    Joined:
    Jul 28, 2012
    Posts:
    25
  24. pyriceti

    pyriceti

    Joined:
    Sep 29, 2015
    Posts:
    8
  25. Vladimir_91

    Vladimir_91

    Joined:
    Jul 20, 2016
    Posts:
    12
    Still waiting
     
  26. kdubnz

    kdubnz

    Joined:
    Apr 19, 2014
    Posts:
    177
    +1 .bump
     
  27. Gooren

    Gooren

    Joined:
    Nov 20, 2015
    Posts:
    332
    +1 bumpity bump
     
  28. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,325
    FYI: Power Inspector can now handle displaying these with names matching the property name. It's automatic (i.e. no need for additional attributes besides SerializeField) and works for things like arrays and lists too.

    serialized-property.png

    Properties that only have a get accessor are shown as well, as read only fields (being able to edit them in the inspector wouldn't be very useful since Unity doesn't serialize them).

    unity-serializefield-get-only-property.png
     
  29. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    212
    Of course, a preferred solution would be fully automatic, i.e. declare the property and it gets serialized if it meets Unity's serialization rules. No attributes.
     
  30. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,325
    @Ramobo That is also possible to pull off today, but you'd need to use a custom serialization solution to serialize all public properties by default. Would be pretty simple to create a base class that does this using the Odin Serializer, I reckon.
     
  31. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,325
    Talking only about public auto-implemented properties, of course.
     
  32. Ramobo

    Ramobo

    Joined:
    Dec 26, 2018
    Posts:
    212
    Except Odin doesn't support interface auto properties, a requirement of mine.
     
  33. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,325
    @Ramobo Oh, really? That is surprising. I haven't really used SerializedMonoBehaviour much, so I had not noticed. I wonder why the limitation exists.

    You can still serialize instances directly with the SerializationUtility, so building a custom serialization system with that would still be a possibility.

    I actually got curious about how hard it would be to pull this off using the Odin Serializer, and ended up throwing together a little test class that will automatically serialize all the surface-level public auto-implemented properties in any extending class. I'll post it here in case someone finds it useful:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Reflection;
    4. using UnityEngine;
    5. using JetBrains.Annotations;
    6. using Sisus.OdinSerializer;
    7. using Object = UnityEngine.Object;
    8.  
    9. namespace Sisus.SerializedProperties
    10. {
    11.     public abstract class MonoBehaviourWithSerializedProperties : MonoBehaviour, ISerializationCallbackReceiver
    12.     {
    13.         [SerializeField, HideInInspector]
    14.         private List<PropertySerializedData> serializedProperties = new List<PropertySerializedData>();
    15.  
    16.         public void OnBeforeSerialize()
    17.         {
    18.             serializedProperties.Clear();
    19.  
    20.             var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    21.             for(int n = fields.Length - 1; n >= 0; n--)
    22.             {
    23.                 string name = fields[n].Name;
    24.                 if(name[0] == '<')
    25.                 {
    26.                     int propertyNameEnds = name.IndexOf('>', 1);
    27.                     if(propertyNameEnds != -1)
    28.                     {
    29.                         var propertyName = name.Substring(1, propertyNameEnds - 1);
    30.                         var property = GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
    31.                         if(property != null && property.GetSetMethod() != null && property.GetCustomAttribute<NonSerializedAttribute>() == null)
    32.                         {
    33.                             var value = property.GetValue(this);
    34.                             if(value != null)
    35.                             {
    36.                                 serializedProperties.Add(new PropertySerializedData(property.Name, value));
    37.                             }
    38.                         }
    39.                     }
    40.                 }
    41.             }
    42.         }
    43.  
    44.         public void OnAfterDeserialize()
    45.         {
    46.             var fields = GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    47.             for(int n = fields.Length - 1; n >= 0; n--)
    48.             {
    49.                 string name = fields[n].Name;
    50.                 if(name[0] == '<')
    51.                 {
    52.                     int propertyNameEnds = name.IndexOf('>', 1);
    53.                     if(propertyNameEnds != -1)
    54.                     {
    55.                         var propertyName = name.Substring(1, propertyNameEnds - 1);
    56.                         var property = GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
    57.                         if(property != null && property.GetSetMethod() != null && property.GetCustomAttribute<NonSerializedAttribute>() == null)
    58.                         {
    59.                             foreach(var serializedProperty in serializedProperties)
    60.                             {
    61.                                 if(string.Equals(serializedProperty.name, property.Name, StringComparison.Ordinal))
    62.                                 {
    63.                                     property.SetValue(this, SerializationUtility.DeserializeValueWeak(serializedProperty.byteData, DataFormat.Binary, serializedProperty.objectReferenceData));
    64.                                 }
    65.                             }
    66.                         }
    67.                     }
    68.                 }
    69.             }
    70.         }
    71.  
    72.         [Serializable]
    73.         public class PropertySerializedData
    74.         {
    75.             public string name;
    76.             public byte[] byteData;
    77.             public List<Object> objectReferenceData;
    78.  
    79.             public PropertySerializedData() { }
    80.  
    81.             public PropertySerializedData(string propertyName, [NotNull]object propertyValue)
    82.             {
    83.                 name = propertyName;
    84.                 byteData = SerializationUtility.SerializeValueWeak(propertyValue, DataFormat.Binary, out objectReferenceData);
    85.             }
    86.         }
    87.     }
    88. }
     
    Ramobo likes this.
  34. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,609
    It's been about a year, any update on this?
     
    Takap_ likes this.
  35. NMD83

    NMD83

    Joined:
    Aug 18, 2014
    Posts:
    3
    +1 on this for sure... To this day I cringe about using public fields or exposing private backing fields.
     
    Takap_ likes this.
  36. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    Unfortunately there has not been any progress on this. I've raised the request with other teams within the company, as my team does not currently not have the bandwidth to look into this.
     
    piersb, olejuer, Takap_ and 3 others like this.
  37. piersb

    piersb

    Joined:
    Mar 13, 2018
    Posts:
    6
    +1
     
    reinfeldx and phobos2077 like this.
  38. sergved

    sergved

    Joined:
    Jul 4, 2019
    Posts:
    1
  39. Cobo3

    Cobo3

    Joined:
    Jun 16, 2013
    Posts:
    67
    bump!
     
    hurleybird likes this.
  40. p-lindberg

    p-lindberg

    Joined:
    Feb 19, 2019
    Posts:
    12
    I was playing around with editor-serialised backing fields and found that you can actually get them to appear nice in the editor by overriding the default property drawer:

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(object), true)]
    2. public class OverridePropertyDrawer : PropertyDrawer
    3. {
    4.     static MethodInfo defaultDraw = typeof(EditorGUI).GetMethod("DefaultPropertyField", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    5.    
    6.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    7.     {
    8.         var newLabel = new GUIContent(label);
    9.         newLabel.text = label.text.Replace("<", "").Replace(">k__Backing Field", "");
    10.         defaultDraw.Invoke(null, new object[3] { position, property, newLabel });
    11.     }
    12. }
    This property drawer will apply to any type that doesn't have its own custom property drawer and strip the backing field parts of the name.

    Obviously, this solution assumes that the backing field will always be named as `<PropertyName>k__BackingField`, which could change in future versions of the compiler.

    I don't think it's a great idea to use serialised backing fields in a production scenario, since, if the naming convention changes for the compiler generated backing field, it will probably break all your serialised data. On the other hand, if you never intend to update your project's Unity version, or you're comfortable enough with regexes to fix your asset files in case the naming convention changes, then this is a viable solution, I think.

    Disclaimer: I haven't tried using this beyond the basic "huh, it works" test.
     
  41. anisimovdev

    anisimovdev

    Joined:
    Mar 4, 2013
    Posts:
    22
  42. ETGgames

    ETGgames

    Joined:
    Jul 10, 2015
    Posts:
    101
    @lukaszunity Can you confirm that it is now officially supported in unity 2020? There are no docs or anything mentioned in the changelog, but it would give me some peace of mind to know it is official...
     
  43. TheZombieKiller

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    265
    It looks like the change was to support the "<PropertyName>k__BackingField" format in ObjectNames.NicifyVariableName.
    Code (CSharp):
    1. // This will now return "My Property"
    2. ObjectNames.NicifyVariableName("<MyProperty>k__BackingField");
    However, the documentation doesn't reflect this yet. Of course, this doesn't address the problem of backing field names being unspecified in any standard, and thus not being future-proof (but realistically, that naming scheme is not likely to change.)
     
  44. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    613
    Bump

    It doesn't work for FindProperty in e.g. Custom Editors yet. Currently I need to do this:
    var graph = serializedObject.FindProperty("<graph>k__BackingField");

    Not the end of the world, but I thought I'd give it a mention.
     
  45. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    So this works and looks nice in the inspector? And the only drawback is that the serialized property name is kind of messed up? Are there any special requirements like Unity version or project settings?
     
  46. piersb

    piersb

    Joined:
    Mar 13, 2018
    Posts:
    6
    Bump. I'd very much like this feature. Can someone confirm or deny its existence?
     
  47. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    If you scroll up the thread, there's people posting examples of it working that are 1 year old, so...
     
    phobos2077 likes this.
  48. piersb

    piersb

    Joined:
    Mar 13, 2018
    Posts:
    6
    The post two above mine says it doesn’t work in a Custom Editor with a FindProperty unless I use something that’s not guaranteed to persist.

    Couldn’t make it work at first go in 2018, and I couldn’t see anything either in this thread or in official documentation confirming which versions of Unity it works in.

    I don’t feel the question was unreasonable, but let’s be more specific: Can someone point me to any Unity docs, confirm which versions it works in, or confirm there’s a solution which doesn’t depend on the auto-generated field always having the same name?
     
    ZenTeapot likes this.
  49. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    Bump.
    Updates and/or actual documentation on this feature would be highly appreciated !
     
    Ali_V_Quest, VolodymyrBS and piersb like this.
  50. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    540
    I found this thread randomly, adding "dummy" fields for properties always annoyed me in Unity. I don't know about custom editors, but with the default inspector this feature works fine, brb removing all my property fields.