Polymorphism and casting

Tutorial

·

intermediate

·

+10XP

·

15 mins

·

(261)

Unity Technologies

Polymorphism and casting

Polymorphism is a feature of inheritance and one of the pillars of object-oriented programming. By the end of this tutorial, you’ll be able to:

  • Explain the basics of polymorphism, including its practical use in development.
  • Upcast and downcast to access different class functionality via polymorphism.
  • Use type checking to avoid errors.
  • Serialize variables correctly when you use polymorphism.

Languages available:

1. Overview

Polymorphism is a feature of inheritance, which means that you can treat class instances as though they were any class that they inherit from. Polymorphism is one of the pillars of object-oriented programming.


By the end of this tutorial, you’ll be able to:


  • Explain the basics of polymorphism, including its practical use in development.

  • Upcast and downcast to access different class functionality via polymorphism.

  • Use type checking to avoid errors.

  • Serialize variables correctly when you use polymorphism.

2. Before you begin

This tutorial is part of Intermediate Scripting, a collection of basic introductions to a wide range of different programming concepts. If you want a guided introductory learning experience, try the Create with Code course or the full Junior Programmer pathway.


3. Basics of polymorphism

In C# scripting, polymorphism is a feature of inheritance. It allows instances of a class to be treated as different types.


Consider the following inheritance hierarchy:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

In this example, Cat inherits from Mammal which in turn inherits from Animal. Polymorphism means that:


  • You can treat instances of Cat as though they are instances of Mammal.

  • You can treat instances of Mammal as though they are instances of Animal.

  • You can also treat instances of Cat as instances of Animal, because if an instance of Cat is a Mammal then it must also be an Animal.

Generally speaking, you can treat an instance of a class as an instance of any class it inherits from. This process is known as upcasting. In the example for this step, an instance of Cat can be upcast to Mammal. This relationship is sometimes referred to as an “Is A” relationship: a Cat “is a” Mammal.


4. Practical use of polymorphism

In practical terms, polymorphism means variables that reference instances of a class can be of a parent type in that instance’s inheritance hierarchy. Let’s consider an expanded example:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Instances of Cat and Dog can be upcast to Mammal, so an array of Mammals can be used to store instances of both these classes.


Important: While you can treat instances of classes as instances of their parent class, functionality from the child class is not available when you do this. In the expanded example above, you could not use one of the elements of the mammals array to call Meow or Woof, but you could use one to call GrowFur.


5. Downcast to access child class functionality

If you need to access the functionality of a child class, you can use a process called downcasting. There are a few techniques associated with downcasting that it is important for you to be aware of in order to avoid errors.


Let’s review an expanded version of the RescueShelter constructor from the previous example to explore proper and improper usage of downcasting.


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

The line which attempts to call the Meow method directly from the mammals array in this example (which has been commented out) would not compile. This compile error would occur because the Mammal class does not have a Meow method, despite the fact that the actual instance the Meow method is being called from is a Cat.


The statement below the commented code assigns to a variable of type Cat from the same element of the array using a cast. The variable being cast (in this case, the first element of an array) is followed by the as keyword and then the type to which it is being cast. If the variable can be converted to that type then it will be returned. If the conversion is not possible then null will be returned.


At the end of the constructor, a variable of type Dog is assigned from the second element of the array using a direct cast. The variable being cast (the second element of an array) is preceded by the type it is being cast to, surrounded by parentheses. If the variable can be converted to that type then it will be returned. If the conversion is not possible then an exception will be thrown.


6. Use type checking to avoid errors

The examples in the previous step demonstrate that an incorrect cast can cause an error. One way that you can avoid this is by using a type checking expression with the is keyword.

Consider this further expansion of the RescueShelter constructor:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

The type checking expressions in this example have a similar syntax to casting using the as keyword. They return true if the conversion is possible and false if it is not. Given this, even if the exact types of the mammals array were unknown, this code would not cause any errors.


7. The SerializeReference attribute

Data that is part of a scene or is part of an asset in Unity is serialized. This essentially means it is converted to another format to be saved to the computer’s drive.


Under normal circumstances, for a class instance to be serialized, the type of the variable which references it must be marked with the Serializable attribute. In the example for this tutorial, the Mammal class definition would need to be marked with the Serializable attribute. When the appropriate conditions are met and you include this attribute, Unity will serialize the data by value. This means that the variable type determines the data that is saved.


Consider a new example:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

When an instance of the PetShop class is added to a GameObject, its data is serialized. In this case, since the mammal variable is being serialized by value, an instance of Mammal will be serialized instead of an instance of Cat.


However, this might not be your desired behavior for serialization. To change this behavior, you can mark the variable with the SerializeReference attribute. This means that the actual instance referenced by the variable will be serialized instead. Here is an example:


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

In this example the mammal variable has been marked with the SerializeReference attribute. Now an instance of Cat will be serialized instead of an instance of Mammal.


Note: You are not not required to mark class definitions with the Serializable attribute to serialize them by reference. However, doing so is recommended so that you can also serialize by value if you need to.


For further details about using the SerializeReference attribute, review the SerializeReference attribute documentation.


8. Summary

In this tutorial, you explored how polymorphism allows you to treat instances as different types within their inheritance structure. You also considered the errors that this can introduce, and reviewed the use of type checking to mitigate these errors. Finally, you reviewed guidance on serializing your variables correctly when you use polymorphism.


How could you use polymorphism to work more efficiently as you develop your own game or application?


Complete this tutorial