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.

Question Unity C#, override variable type in child class

Discussion in 'Scripting' started by sergioramirezrubio, Jan 2, 2024.

  1. sergioramirezrubio

    sergioramirezrubio

    Joined:
    Jun 5, 2020
    Posts:
    3
    Hello!
    I'll try to do my best to explain myself, but English is not my first language.

    In summary: I want to override a variable type in a child class or something similar so, my child classes have always the "latest" version of each class.

    Right now, I have 2 classes > Animal and AnimalAnimator and both classes has their child class Dog and DogAnimator.

    Animal class looks like this:
    Code (CSharp):
    1.  
    2. public class Animal : MonoBehaviour
    3. {
    4.     public AnimalAnimator Animator { get; private set; }
    5.  
    6.     private virtual void Awake()
    7.     {
    8.         Animator = GetComponent<AnimalAnimator>();
    9.         [code...]
    10.     }
    11. }
    I want to do something like this with Dog:
    Code (CSharp):
    1.  
    2. public class Dog : Animal
    3. {
    4.    public new DogAnimator Animator { get; private set; }
    5.  
    6.    private override void Awake()
    7.    {
    8.        base.Awake();
    9.  
    10.        Animator = GetComponent<AnimalAnimator>();
    11.    }
    12. }
    I want Dog to extend Animal, and override the AnimalAnimator component with DogAnimator (that extend DogAnimator. But Unity is giving me this error:
    Code (Boo):
    1. The same field name is serialized multiple times in the class or its parent class. This is not supported: Base(Dog) <Animator>k__BackingField
    Is this possible in C#? How can I proceed?

    Thank you very much.
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,661
    No, that's not a thing.

    But since your Dog class is already aware of all your DogAnimator needs, just make a second field and use it instead of the base field.

    Code (CSharp):
    1. public class Dog : Animal
    2. {
    3.    public DogAnimator dogAnimator { get; private set; }
    4.    private override void Awake()
    5.    {
    6.        base.Awake();
    7.        dogAnimator = (Animator as DogAnimator);
    8.    }
    9. }
     
    Bunny83 likes this.
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,261
    Right. Overriding of variables isn't even a thing. You can override properties (which would be the case here). However when overriding a method / property you can not change the type or signature.

    Implementing a new property is probably the best approach. However I wouldn't create a second backing field / auto-property. Even though it's a slight performance penalty, I would recommend to create a property like this:

    Code (CSharp):
    1. public DogAnimator dogAnimator
    2. {
    3.     get => (DogAnimator)Animator;
    4.     private set => Animator = value;
    5. }
    In addition you "may" want to declare the Animator property as virtual, so it can actually be overwritten, and also override the Animator property to prevent the assignment of illegal AnimalAnimators and only allow DogAnimator(s).

    Code (CSharp):
    1. public override AnimalAnimator Animator
    2. {
    3.     get => base.Animator;
    4.     private set
    5.     {
    6.         if (value is DogAnimator dogA)
    7.             base.Animator = dogA;
    8.         else
    9.             throw new System.Exception("Can't assign non DogAnimator");
    10.     }
    11. }
    The point of having a base class is to have a standard interface so all derived classes can be treated like it's just a base class (which it actually is). Don't just create class hierarchies because you can. You really should think about if you really need polymorphism. In many cases using interfaces is more flexible and doesn't create the usual straight-jackets that class inheritance brings to the table.
     
    SisusCo likes this.
  4. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,393
    Or you could use generics:
    Code (CSharp):
    1. public abstract class Animal<TAnimator> : MonoBehaviour where TAnimator : AnimalAnimator
    2. {
    3.     [field: SerializeField]
    4.     public TAnimator Animator { get; private set; }
    5. }
    Code (CSharp):
    1. public class Dog : Animal<DogAnimator> { }
    Also I believe your original attempt should work perfectly fine if you just remove
    [field: SerializeField]
    from the Animator property in all the derived types.
    Code (CSharp):
    1. public class Animal : MonoBehaviour
    2. {
    3.     [field: SerializeField]
    4.     public AnimalAnimator Animator { get; private set; }
    5. }
    6.  
    7. public class Dog : Animal
    8. {
    9.     //[field: SerializeField] <- remove this
    10.     public new DogAnimator Animator { get; private set; }
    11. }
    It's only Unity's serialization system that is causing the warning to occur.
     
    Last edited: Jan 2, 2024
    sergioramirezrubio likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,261
    Note that generics essentially has the opposite goal than polymorphism. Your "Dog" and a potential "Cat" instance would be completely incompatible with each other. Generics actually diverge classes into distinct separate classes which has the same functionality but with different types.

    Polymorphism is about abstracting a concept and have different implementation which can be interfaced in a general way. That's why the method definitions are the same. So inheritance means you inherit all the data and methods from your parent but you can change / override the method implementation.

    Generics is about providing a certain algorithm that works the same on different types. So here you inherit the functionality but work on different data. That's why you can't treat the same generic class with different generic arguments in a generic way. The best example is the
    List<T>
    class. The functionality the class provides is set in stone and works the same regardless of the type used. However a
    List<int>
    and a
    List<string>
    are completely separate classes and can never be treated in some generic way.

    So like I said in my last post, be careful to not step into a dead end as inheritance and generics put you into a straight-jacket.
     
    sergioramirezrubio likes this.
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,393
    Good point @Bunny83 ! To create methods that work with all different types of Animal<TAnimator> types, those methods would also need to be generic. So if those public properties are accessed a lot from the outside, it could become a big nuisance.
    Code (CSharp):
    1. public static class AnimalUtils
    2. {
    3.     public static void Animate<TAnimator>(Animal<TAnimator> animal, AnimalAnimation animation) where TAnimator : AnimalAnimator
    4.         => animal.Animator.Play(animation);
    5. }
    To avoid this, a non-generic base class, or an interface, could be added:
    Code (CSharp):
    1. public interface IAnimal
    2. {
    3.     AnimalAnimator Animator { get; }
    4. }
    5.  
    6. public abstract class Animal<TAnimator> : MonoBehaviour, IAnimal where TAnimator : AnimalAnimator
    7. {
    8.     [field: SerializeField]
    9.     public TAnimator Animator { get; private set; }
    10.  
    11.     AnimalAnimator IAnimal.Animator => Animator;
    12. }
    13.  
    14. public class Dog : Animal<DogAnimator> { }
    This would simplify accessing the AnimalAnimator from the outside:
    Code (CSharp):
    1. public static class AnimalUtils
    2. {
    3.     public static void Animate(IAnimal animal, AnimalAnimation animation)
    4.         => animal.Animator.Play(animation);
    5. }
    Generics is also a form of polymorphism (parametric polymorphism), like subtyping is, but it's an entirely different approach, so the two can't be mixed and matched freely.