Understanding the Third Pillar of OOP : C#'s Polymorphic Support
Using the virtual and override Keywords
Polymorphism provides a way for a subclass to define its own version of a method defined by its base class, using the process termed "method overridden". If a base class wants to define a method that may be (but does not have to be) overridden by a subclass ,it must mark the method with the "virtual" keyword.
partial class Employee
{
public double Pay { get; set; }
// This method can new be "overriden" by a derived class.
public virtual void GiveBonus(float amount)
{
Pay += amount;
}
}
Note: Methods that have been marked with the virtual keyword are (not surprisingly) termed methods.
When a subclass wants to change the implementation details of a virtual method, it does so using the override keyword.
class SalesPerson : Employee
{
public override void GiveBonus(float amount)
{
// Overriden code
}
}Sealing Virtual Members
On a related note, sometimes you might want to seal an entire class but simply want to prevent derived types from overridding particual virtual methods.
class SalesPerson : Employee
{
public override sealed void GiveBonus(float amount)
{
// Overriden code
}
}
Thus, if you attempted to override this method in the PTSalesPerson class, you'd receive compile-time errors.
class PTSalesPerson : SalesPerson
{
// ERROR!
public override void GiveBonus(float amount)
{
}
}
New in C# 10, the ToString() method for a record can be sealed, preventing the compiler from synthesizing a ToString() method for any derived record types.
public record CarRecord
{
public string Make { get; init; }
public sealed override string ToString()
{
return $"The Make = {Make}";
}
}
Understanding Abstract Classes
// What does it mean ?
Employee x = new Employee();
Given that many base classes tend to be rather nebulous entities, a far better design for this example is to prevent the ability to directly create a new Employee object in code. In C#, you can enforce this programmatically by using the "abstract" keyword in the class definition.
abstract partial class Employee { }
// Error!
Employee x = new Employee();Understanding the Polymorphic Interface
When a class has been defined as an abstract base class (via abstract keyword), it may define any number of abstract members. Abstract members can be used whenever you want to define a member that does not supply a default implementation but must be accounted for by each derived class. By doing so, you enforce a polymorphic interface on each descendant, leaving them to contend with the task of providing the details behind your abstract methods.
abstract class Shape
{
public string PetName { get; set; }
protected Shape(string name = "NoName")
{
PetName = name;
}
public virtual void Draw()
{
Console.WriteLine("In Shape");
}
}
class Circle : Shape
{
public Circle() { }
public Circle(string name) : base(name) { }
}
class Hexagon : Shape
{
// constructors like circle.
public override void Draw()
{
Console.WriteLine("In Hexa");
}
}
Note: Abstract methods can be defined only in abstract classes. If you attempt to do otherwise, you will be issued a compiler error.
Methods marked with abstract are pure protocol. They simply define:
- The name
- return type (if any)
- parameter set (if required)
in the Shape abstract class:
public abstract void Draw();
Shape[] ms = { new Circle("Beth"), new Circle(), new Hexagon() };
foreach(Shape m in ms)
{
m.Draw();
}
This code illustrates polymorphism at its finest. Although it is not possible to directly create an instance of an abstract base class (the Shape), you are able to freely store references to any subclass with an abstract base variable. Therefore, when you are creating an array of Shapes, the array can hold any object deriving from the Shape base class (if you attempt to place Shape - incompatible objects into the array, compiler error).
Given that all items in the ms array do indeed derive from Shape, you know they all support the same "polymorphic interface" (or said more plainly, they all have a Draw()(method.)
Understanding Member Shadowing
C# provides a facility that is the logical opposite of method overriding termed shadowing. Formally speaking, if a derived class defines a member that is identical to a member defined in a base class the derived class has shadowed the parent's version. In the real world, the possibility of this occuring is the greatest when you are subclassing from a class.
You (or your team) did not create yourself.
class ThreeDCircle : Circle
{
public void Draw()
{
Console.WriteLine("Drawing 3D Circle"); // We got warning, no compile error.
}
}
The problem is that you have a derived class (ThreeDCircle) that contains a method that is identical to an inherited method.
To address this issue, you have a few options. You could simply update the child's version of Draw() using the override keyword. With this approach, the ThreeDCircle type is able to extend the parent's default behavior as required. However, if you don't have access to the code defining the base class, you would be unable to modify the Draw() method as a virtual member, as you don't have access to the code file!
As an alternative, you can include the "new" keyword to the offending Draw() member of the derived type. Doing so explicitly states that the derived type's implementation is intentionally designed to effectively ignore the parent's version.
In ThreeDCircle class:
public new void Draw()
{
Console.WriteLine("Drawing 3D Circle"); // We got warning, no compile error.
}
You can also apply the "new" keyword to any member type inherited from a base class (field,constant,static member,or property).
public new string PetName { get; set; }