Search Unity

  1. Want to see 2020.1b in action? Sign up for our Beta 2020.1 Overview Webinar on April 20th for a live presentation from our evangelists and a Q&A session with guests from R&D.
    Dismiss Notice
  2. Interested in giving us feedback? Join our online research interviews on a broad range of topics and share your insights with us.
    Dismiss Notice
  3. We're hosting a webinar for the new Input System where you'll be able to get in touch with the devs. Sign up now and share your questions with us in preparation for the session on April 15.
    Dismiss Notice
  4. 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:
    4,625
    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

    Unity Technologies

    Joined:
    Jun 11, 2014
    Posts:
    443
    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.
     
    Ryiah, Xarbrough, Peter77 and 10 others like this.
  3. Kitsuba

    Kitsuba

    Joined:
    Mar 5, 2015
    Posts:
    13
    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:
    133
    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.
     
    ArthurAulicino, mvaz_p and phobos2077 like this.
  5. IgnisIncendio

    IgnisIncendio

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

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    55
    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
     
  7. IgnisIncendio

    IgnisIncendio

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

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    133
    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

    Unity Technologies

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

    Artifact-Jesse

    Joined:
    May 5, 2013
    Posts:
    6
    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:
    93
    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.
     
    phobos2077 likes this.
  15. DanielSuperKing

    DanielSuperKing

    Joined:
    Mar 26, 2019
    Posts:
    11
    I honestly want this MORE than any other possible Unity feature right now.
     
    phobos2077 likes this.
  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)
     
  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:
    81
    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.
     
    Stardog, Ryiah, mswain_tlon and 5 others like this.
  21. ZenTeapot

    ZenTeapot

    Joined:
    Oct 19, 2014
    Posts:
    10
    same. bump
     
  22. rerwandi

    rerwandi

    Joined:
    Dec 8, 2014
    Posts:
    531
    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:
    13
  24. pyriceti

    pyriceti

    Joined:
    Sep 29, 2015
    Posts:
    1
  25. VladvladLem

    VladvladLem

    Joined:
    Jul 20, 2016
    Posts:
    2
    Still waiting
     
  26. kdub

    kdub

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

    Gooren

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

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    413
    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:
    106
    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:
    413
    @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:
    413
    Talking only about public auto-implemented properties, of course.
     
  32. Ramobo

    Ramobo

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

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    413
    @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:
    4,653
    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

    Unity Technologies

    Joined:
    Jun 11, 2014
    Posts:
    443
    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.
     
    olejuer, Takap_, hurleybird and 2 others like this.
  37. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    4,653
  38. piersb

    piersb

    Joined:
    Mar 13, 2018
    Posts:
    1
    +1
     
    phobos2077 likes this.
  39. sergved

    sergved

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

    Cobo3

    Joined:
    Jun 16, 2013
    Posts:
    64
    bump!
     
unityunity