Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Resolved Single line self initializing variable

Discussion in 'Scripting' started by OhDangTheJam, May 3, 2022.

  1. OhDangTheJam

    OhDangTheJam

    Joined:
    Feb 1, 2017
    Posts:
    2
    So I'm trying to do something like this:

    Code (CSharp):
    1. private Image image { get { return image ?? GetComponent<Image>() } }
    Obviously this will throw an error since it is attempting to get itself inside it's own get, but is there a clean way to do this in a single line that doesn't involve making another proxy variable/initializing in a Start() or Awake()?

    If it's not obvious I'd just like to have a script on a prefab that will initialize the image reference the first time a get is called on the variable. My game is turn based and I'm not concerned about performance using GetComponent in this instance, I'm just trying to make cleaner code with less needless drag & drop in the editor.
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,261
    I think you're confusing something here. This:

    Code (CSharp):
    1. private Image image { get { return ....; } }
    is NOT a variable. This is just a property. There is no memory anywhere to store anything. This is just fancy syntax for a method, nothing more. So this is exactly the same as

    Code (CSharp):
    1. public Image GetImage()
    2. {
    3.    return ....;
    4. }
    There is no variable here. You "may" think about another little feature called "auto-property". The auto property syntax looks like this

    Code (CSharp):
    1. private Image image { get; set; }
    This is still a property and not a variable, however the auto property actually generates a hidden backing field. So an auto property actually does this for you:

    Code (CSharp):
    1.  
    2. private Image m_Image;
    3. private Image image { get { return m_Image;} set {m_Image = value;} }
    When you create a normal property where you define the body of your getter / setter yourself, it's not an auto property and there is no backing field that is generated for you.

    So if you want a property that does lazy initialize itself when you read it, you have to create the backing field yourself.
     
    Kurt-Dekker and Dextozz like this.
  3. OhDangTheJam

    OhDangTheJam

    Joined:
    Feb 1, 2017
    Posts:
    2
    That really helped my understanding of normal/auto properties. Thanks so much for the detailed reply.

    For anyone else who finds this googling, the solution was:

    Code (CSharp):
    1. private Image _image;
    2.     private Image image
    3.     {
    4.         get { return _image ?? GetComponent<Image>(); }
    5.         set { _image = value; }
    6.     }
    It is not currently possible to do in a single line.
     
    Last edited: May 3, 2022
    Bunny83 likes this.
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,647
    You could use the null assignment operator which will assign the field should it be null:
    Code (CSharp):
    1. public Image Image => _image ??= GetComponent<Image>();
    (Should be correct, not actually at an IDE).

    HOWEVER: It's generally recommended not to use null propagating, null coalescing, etc, with UnityEngine.Objects. As the operators can't be overridden they do not perform the same checks as
    ==/!= null
    which can lead to odd behaviour in certain situations.

    So for the moment you should just do vanilla C# here:
    Code (CSharp):
    1. private Image _image;
    2.  
    3. private Image Image
    4. {
    5.     get
    6.     {
    7.         if (_image == null)
    8.         {
    9.             _image = GetComponent<Image>();
    10.         }
    11.         return _image;
    12.     }  
    13. }
    14.  
     
    Bunny83 likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,261
    Right. Though you could use the ternary operator instead, if a "single line" is really that important ^^

    Code (CSharp):
    1. public Image Image => (_image!=null)?_image:_image = GetComponent<Image>();
    This should work, but I really, really do not recommend this kind of code. If you want such logic for a general purpose, you may want to do something like this instead:
    Code (CSharp):
    1. public static class ComponentExt
    2. {
    3.     public static T GetCachedComponent<T>(this Component aComp, ref T aCache) where T : class
    4.     {
    5.         if (aCache is UnityEngine.Object o && o != null)
    6.             return aCache;
    7.         aCache = aComp.GetComponent<T>();
    8.         return aCache;
    9.     }
    10. }
    This this extension method in your project you can declare your properties like this

    Code (CSharp):
    1.     private Image _image;
    2.     public Image Image => this.GetCachedComponent(ref _image);
    Note that the "this" is necessary here in order for the compiler to "see" the extension method.

    ps: This does also work with interfaces, just in case you're wondering about the strange null check.
     
    Last edited: May 4, 2022
    spiney199 likes this.