Search Unity

Additional uses for FormerlySerializedAs

Discussion in 'Scripting' started by Kartack, Nov 22, 2019.

  1. Kartack

    Kartack

    Joined:
    Nov 7, 2012
    Posts:
    11
    Hey, the FormerlySerializedAs attribute is incredibly useful if you find that you need to change the name of a serialized field on a class. I was wondering if it would be possible for us to get the same thing for classes, so that we could rename them in the same way. Basically exactly the same syntax just to a different kind of type.

    As well FormerlySerializedAs helps if you need to change the name of the field but doesn't help if you have to change the type. It would also be cool if we could have another use of FormerlySerializedAs where we provide a System.Func which takes as input the data field that was serialized and outputs the new field type. So for example if we had a class like:

    public class ExampleClass : MonoBehaviour {
    public Transform mountPoint;
    }

    and wanted to change it to:

    public class ExampleClass : MonoBehaviour {
    public GameObject mountPoint;
    }

    we could do something like:
    public class

    public class ExampleClass : MonoBehaviour {
    [FormerlySerializedAs((Transform x) => x.gameObject)]
    public GameObject mountPoint;
    }

    and when it deserializes the class if it finds a reference to a transform in the mountPoint field it will use the provided System.Func to transform the data to the new type and then on next serialization will write the new type data into the field.

    Thanks!
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Short answer - its not possible.

    Long answer - in C# those values you define into attributes are "plugged-in" at the time of the constructor of the object runs. And by the time it runs - they had to be resolved. This creates a problem, that before constructor - gameObject property simply do not exist.

    Not mentioning that value must be a constant, (which is a limitition of the compiler) and gameObject is definitely is not a constant. As well as System.Func.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    No...

    Attributes, and the values that go into them, are only ran if you access the attribute (usually done through reflection). You actually don't even need an instance of the class that attribute is used inside of to do so. Attributes are part of the class declaration, not the object instance. They maintain no state relative to the object.

    Furthermore gameObject not being available at the time is irrelevant to the problem. OP's lamda function is perfectly fine because they're accessing the gameObject property of 'x', where 'x' is a reference to a Transform passed to the lamda. And it would only ever get ran once the attribute was created, and the delegate property accessed and called (if attributes supported such a thing).

    "const" though is the problem. Technically. Attribute's only support constants/literals of a handful of types (numeric types, string, bool, enums, System.Type, as well as 1-d arrays of those types). I say technically because, technically not all of those are consts. So it supports constant/literals as well as a very limited set of referenced types (Type and 1-d arrays).

    A way this could be gotten around is using a string name of a function to call. And require that function to be a static function member of the class.

    But that's just to get around the language barrier... really the issue is to get Unity to implement such a thing.

    ...

    @OP

    The best Unity has given (beyond the FormerlySerializedAs attribute) is the ISerializationCallbackReceiver interface:
    https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

    And technically you can get what OP desires with this... it just would require keeping the old property/field and adding a new one for the new data type. Super annoying, but it'd work.

    Personally I wish Unity implemented ISerializationCallbackReceiver in a way more similar to other serialization engines where a data context is passed in. Think something like C#'s built in ISerializable interface:
    https://docs.microsoft.com/en-us/do...alization.iserializable?view=netframework-4.8
    Note that you receive a 'SerializationInfo' which holds all the serialized members by name and you can read/write to it. Converting the type that comes out of the SerializationInfo is trivial at this point.

    But alas, Unity did NOT do this. :(


    As for this part:
    You have 2 different paragraphs talking about changing types. The 2nd paragraph you give examples and it's to change the type the field/property that it references to another type that is not polymorphic to the original type (like GameObject to Transform).

    But the former is a little vague. And I can't tell if you're expanding on the same topic, or talking about 2 different things (you say "as well", which implies a second topic. Also, it's in a second paragraph which also implies that).

    I have a couple ways to take this...

    Do you mean change the name of the class. Like changing:
    public class MyScript : MonoBehaviour

    to:
    public class MyScriptRenamed : MonoBehaviour

    ?

    Cause if so... both Unity and Visual Studio support the renaming of classes just fine. You just have to make sure the meta file also gets renamed with it.

    Or do you mean that you want to change the type the field/property references to a type that IS PolyMorphic... like Component -> Transform or something like that? Which also is already supported without the need for attributes.
     
    Last edited: Nov 22, 2019
    xVergilx likes this.
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Yeah, completely forgot that attributes are class related, not object related.
    And I was doing reflection / inspector stuff with them today. :p

    Also, Jetbrains Rider refactors them via "Rename" without anything extra done on top.
     
  5. v01pe_

    v01pe_

    Joined:
    Mar 25, 2015
    Posts:
    71
    I'd wish for this as well! Here are some stupid examples of what would be nice, if `FormerlySerializedAs` supported it:

    changing type:
    Code (CSharp):
    1. //public float justAFloat;
    2. [FormerlySerializedAs("justAFloat", "RoundFloat")]
    3. public int nowAnInt;
    4. private static int RoundFloat(float f) => (int)(f+0.5f);
    refactoring into struct:
    Code (CSharp):
    1. //public int numerator;
    2. //public int denominator;
    3. [FormerlySerializedAs("numerator", "denominator", "CreateRational")]
    4. public Rational rational;
    5.  
    6. [Serializable]
    7. public struct Rational
    8. {
    9.     public int n;
    10.     public int d;
    11. }
    12. private static Rational CreateRational(int num, int det)
    13. {
    14.     return new Rational { n = num, d = det };
    15. }
     
  6. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44