Virtual Methods and Properties
Explore how to use virtual methods and properties in C# to implement polymorphism. Learn to override base class methods and properties with specific behaviors in derived classes, control inheritance with sealed keywords, and apply base method calls. This lesson helps you understand key object-oriented programming concepts in C#.
We'll cover the following...
When we inherit a method from a base class, we might need to change its behavior in a child class. This ability to provide specific implementations for a general action is a core pillar of polymorphism in C#.
Consider the following example:
public class Animal{public void Voice(){// Method implementation}}
Suppose the Voice() method produces the voice of an animal. For something as generic as the Animal class, we could have a generic implementation of this method. However, derived classes like Cat or Dog require specific implementations of the Voice() method. For example, cats meow and dogs bark.
Providing a different implementation for an inherited method is called overriding. In C#, we can only override a method if it was marked as virtual in a base class.
Overriding a method
We have three classes: Animal, Cat, and Dog. The latter two classes inherit the Voice() method from Animal but override it with different implementations.
First, let's look at the base class where the method is defined.
Line 1: We declare a file-scoped namespace.
Line 7: We use the
virtualkeyword to allow derived classes to override theVoicemethod.Line 9: We provide a default implementation that prints “Grrrrr”.
Now that we have a base class with a virtual method, we can create a derived class that provides a specific implementation.
Line 4: We define the
Catclass, inheriting fromAnimal.Line 7: We use the
overridekeyword to replace the base class implementation ofVoice.Line 9: We provide the specific implementation for
Cat, printing “Meow...”.
Similarly, we can create another derived class that offers a different behavior for the same method.
Line 4: Defines the
Dogclass, inheriting fromAnimal.Line 7: Uses the
overridekeyword to replace the base class implementation ofVoice.Line 9: Provides the specific implementation for
Dog, printing “Bark! Bark!”.
With all our classes defined, we can now instantiate them and observe how the runtime calls the correct method for each object.
Line 3: Creates an instance of the base class
Animal.Lines 4–5: Creates instances of the derived classes
DogandCat.Line 7: Calls
Voiceon the base object, which prints “Grrrrr”.Lines 8–9: Calls
Voiceon the derived objects. The runtime executes the overridden versions, printing “Bark! Bark!” and “Meow...” respectively.
There are several constraints to overriding:
The overridden version must have the same access modifiers as the base class. For instance, if a virtual method is
public, the overridden version must also bepublic.We cannot override a
staticmethod. Static methods cannot be declaredvirtual.
The base keyword
Sometimes we want to call the base version of the method from within the method we are overriding. This occurs if the base version performs generic operations and our override extends this functionality. We can call the base version using the base keyword.
public override void Voice(){base.Voice(); // Calling the base version of this methodConsole.WriteLine("Extended functionality.");}
Line 1: Declares an override of the
Voicemethod.Line 3: Uses
base.Voice()to execute the code inside the parent class’sVoicemethod before continuing.Line 4: Executes the additional code specific to this derived class.
Overriding properties
In C#, we can also override properties. Since property getters and setters are methods, overriding a property effectively overrides these accessors.
In the following example, we define a Number class with a virtual property, and a NonNegativeNumber class that prevents the value from being negative.
Line 6: Declares an auto-implemented property
Valuemarked asvirtual.Line 9: Defines
NonNegativeNumberinheriting fromNumber.Line 14: Overrides the
Valueproperty using theoverridekeyword.Lines 16–19: Implements the
getaccessor to return the backing field_value.Lines 21–28: Implements the
setaccessor with logic to ignore negative values.
Now, let’s see how these properties behave when accessed from the main program.
Line 3: Instantiates the base
Numberclass and setsValueto -50. This is allowed.Line 8: Instantiates
NonNegativeNumberand attempts to setValueto -50.Line 11: Prints the value of the base object (-50).
Line 14: Prints the value of the derived object. Since the overridden setter rejected the negative number, it remains 0.
The sealed keyword
To prevent further overriding of a method in derived classes, we can mark it as sealed. This terminates the inheritance chain for that member.
In this example, we create a Wolf class that inherits from Dog (defined earlier). The Wolf class overrides the Voice method and seals it.
Line 3: Defines a
Wolfclass inheriting fromDog.Line 6: Uses
sealed overrideto provide an implementation ofVoice.Line 8: Any class inheriting from
Wolfwill be unable to overrideVoicefurther.
If we attempt to inherit from Wolf and override the Voice method again, the compiler will produce an error.
Line 3: Defines
MutantWolfinheriting fromWolf.Lines 6–9: Attempts to override
Voice. This code is commented out because it causes a compilation error (CS0239). Thesealedkeyword inWolfprevents this modification.