C#. Late and early binding. Polymorphism. Basic concepts. Examples. Passing a reference to a base class in a method




Late and early binding. Polymorphism. Basic concepts. Examples. Passing a reference to a base class in a method. Keywords virtual, override, new

This topic is a continuation of the topic:


Contents


Search other websites:

1. The concept of late and early binding. Keywords virtual, override

When studying the topic of polymorphism, it is important to understand the concept of late and early binding, which is used by the compiler to build program code in case of inheritance. If classes form an inheritance hierarchy, then when accessing the elements of a class, the compiler can implement one of two possible ways of linking code:

  1. Early binding – associated with the formation of code at the compilation stage. With early binding, the program code is generated based on known information about the type (class) of the reference. Typically, this is a reference to the base class in the class hierarchy.
  2. Late binding – associated with the formation of code at run time. If a chain of virtual methods is found in the class hierarchy (using the words virtual, override), then the compiler builds the so-called late binding. With late binding, the method is called based on the type of the object, not the type of reference to the base class. Late binding is used when polymorphism needs to be implemented.

The choice of this or that type of binding for each individual element (method, property, indexer, etc.) is determined by the compiler according to the following rules:

  • if a non-virtual element is declared in the hierarchy of inherited classes, then early binding is implemented;
  • if a virtual element is declared in the hierarchy of inherited classes, then later binding is performed (Figures 1, 2). The virtual element in the base class is indicated by the virtual keyword, in all inherited classes by the keyword override. In C#, a virtual element can be a method, event, indexer, or property.

The necessary conditions for the implementation of late binding:

  • classes must form an inheritance hierarchy;
  • classes must have methods with the same signature. Elements (methods) of derived classes must override the corresponding elements (methods) of base classes;
  • the elements (methods) of the class must be virtual, that is, they must be indicated by the keywords virtual, override.

Figure 1 shows an example that displays the difference between late and early binding for the example of two classes A, B in which the Print() method is implemented.

C#. Inheritance. Late and early binding. Differences

Figure 1. Late and early binding. Differences

In case of early binding, as soon as the compiler encounters a string

A ref;

the reference ref is declared, which is of the type of base class A. Further assignment

refA = objB;

binds the reference to the objB object, however the type of the reference is set to A. Therefore a call

ref.Print();

will call the Print() method of class A.

In case of late binding, first, based on the description of classes A, B, the compiler determines that the Print() method is virtual. For a virtual method, the compiler builds a table of virtual methods Print(), which contains the offset address of each virtual method for each class of the hierarchy (this is a separate topic for research).

After line

A ref;

binding of ref to type A is formed. After assignment

ref = objB;

the compiler assigns the ref address to the objB instance address and defines the binding type as type B (since the Print() method is virtual). The type of object is taken as a basis. As a result, the ref is associated with the Print() method, which is implemented in class B (and not in class A) – the so-called “late binding” is performed.

As a result, after the call

ref.Print();

the Print() method of class B will be called.

Figure 2 shows the difference between late and early bindings using the example of three classes A, B, C, in each of which the Print() method is declared.

C#. Inheritance. Early and late binding for the method on the example of three classes

Figure 2. Early and late binding for the Print() method on the example of three classes A, B, C. Call the Print() method by reference to an object of class C

 

2. What is polymorphism? Dynamic polymorphism

Polymorphism is a property of program code to change depending on the situation that occurs at the time of program execution.

The main principle of polymorphism is one interface, many implementations (methods). In terms of a programming language, polymorphism is the ability, using a reference to a base class, to access the elements (methods) of instances of inherited classes in a single unified way.

Taking advantage of polymorphism is possible in situations:

  • when classes form a hierarchy using the concept of inheritance;
  • when the classes that form the hierarchy have elements (methods, properties, etc.) with the same signature. In such cases, the concept of “method override” arises.

In the C # programming language, polymorphism is provided using the virtual and override keywords. Using these keywords provides dynamic polymorphism. The term “dynamic” means that a virtual element is called dynamically during program execution, depending on the type of object for which this element is called.

 

3. What class elements can polymorphism be used for?

Polymorphism can be applied to the following elements:

  • methods;
  • properties;
  • indexers;
  • events.


 

4. Schematic explanation of polymorphism

Figure 3 demonstrates the use of polymorphism for two classes.

C#. Inheritance. The implementation of polymorphism on the example of two classes

Figure 3. The implementation of polymorphism on the example of two classes A, B

 

5. Polymorphism in the case of passing to the method a reference to the base class. Late binding

A reference to the base class can be passed to any method. Using this reference, you can also call methods whose properties support polymorphism.

Example.

Two classes are given with the names Base and Derived. The Derived class inherits from the Base class. In both classes, the virtual method Info() is defined, which displays information about the class. The virtual method Info() is called from the static ShowInfo() method of the Program class. The ShowInfo() method receives a reference to the base class as an input parameter and, through this reference, accesses the Info() method of the class. Depending on which instance of the class is passed to the ShowInfo() method, the corresponding Info() method is called.

using System;

namespace ConsoleApp5
{
  // Passing a reference to a base class in a method
  class Base
  {
    // Virtual method Info()
    public virtual void Info()
    {
      Console.WriteLine("Base.Info()");
    }
  }

  class Derived : Base
  {
    // Virtual method Info() of inherited class
    public override void Info()
    {
      Console.WriteLine("Derived.Info()");
    }
  }

  class Program
  {
    // Static method ShowInfo() - gets a reference
    // to the base class Base as a parameter
    static void ShowInfo(Base r)
    {
      // Invoke the method Info() using a reference.
      // In this method, it is not known
      // which class instance r points to: Base or Derived?
      // The compiler will generate code
      // at runtime - this process is called late binding.
      r.Info(); // single interface (call) for all instance implementations
    }

    static void Main(string[] args)
    {
      // Demonstration of polymorphism and late binding
      // 1. Declare a reference to the base class
      Base rB;

      // 2. Create instances of the Base and Derived classes
      Base oB = new Base();
      Derived oD = new Derived();

      // 3. Call the ShowInfo () method with an rB parameter
      //   that points to an oB instance of the Base class
      rB = oB;
      ShowInfo(rB); // invokes Base.Info()

      rB = oD;
      ShowInfo(rB); // invokes Derived.Info()

      // 4. Calling the ShowInfo() method,
      //   oB and oD objects are passed to the method
      ShowInfo(oB); // invokes Base.Info()
      ShowInfo(oD); // invokes Derived.Info()

      // Conclusion: the ShowInfo() method at the compilation
      // stage does not know which class instance will be
      // passed to it as a parameter. The ShowInfo() method
      // implements a uniform call to the Info: r.Info()
      // method for all instances that will be passed
      // to it - this is called the "single interface".
    }
  }
}

 

6. What requirements are imposed on an element of a class in order to support polymorphism?

In order for a class element (for example, a method) to support polymorphism, it must be made virtual. For a class element to be virtual, the following requirements must be met:

  • in the base class, this element (method, property) should be designated as virtual or abstract. The abstract keyword also makes the element virtual. This word is used if the class element is abstract. More details about abstract classes are described here;
  • in derived classes, elements of the same name must be designated as override. If in a derived class you need to implement a non-virtual method whose name matches the virtual method of the base class, then this method is indicated by the new keyword (see clause 7).

 

7. Using the new keyword in a chain of virtual methods. Example

As you know, an element of a class that is declared virtual conveys the ability to implement polymorphism in elements of the same class with inherited classes. Thus, virtual elements form a chain down the hierarchy. To ensure that an element of a class that overrides a virtual element of a base class does not support polymorphism, the new keyword must be specified. If there is one non-virtual method (with the new keyword) in the chain of the same name virtual methods, then this method breaks the chain.

Example. Classes with the names A1, A2, A3, A4 are specified. Classes form a hierarchy of inheritance. In class A3, the Print() method is declared with the new keyword breaking the chain of virtual methods.

The text of the demo program is as follows.

using static System.Console;

namespace ConsoleApp5
{
  // Base class in the hierarchy
  class A1
  {
    // Virtual method Print()
    virtual public void Print()
    {
      WriteLine("A1.Print()");
    }
  }

  // Class, derived from class A1
  class A2 : A1
  {
    // virtual method Print()
    override public void Print()
    {
      WriteLine("A2.Print()");
    }
  }

  // Class, derived from class A2
  class A3 : A2
  {
    // Non-virtual method Print () - this method
    // breaks the chain of virtual methods, the method
    // is indicated by the keyword new
    new public void Print()
    {
      WriteLine("A3.Print()");
    }
  }

  // Class, derived from class A3
  class A4 : A3
  {
    // Again the non-virtual Print () method. The override keyword
    // cannot be set here because the chain of virtual
    // methods is broken.
    new public void Print() // only new can be used
    {
      WriteLine("A4.Print()");
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 1. Reference to the base class
      A1 refA1;

      // 2. Instances of classes
      A1 objA1 = new A1();
      A2 objA2 = new A2();
      A3 objA3 = new A3();
      A4 objA4 = new A4();

      // 3. Call the Print () method of instances
      //    A1, A2, A3, A4 via refA1 reference
      refA1 = objA1;
      refA1.Print(); // A1.Print - base class method

      // objA2
      refA1 = objA2;
      refA1.Print(); // A2.Print - polymorphism

      // objA3
      refA1 = objA3;
      refA1.Print(); // A2.Print - no polymorphism
      (refA1 as A3).Print(); // A3.Print - static polymorphism

      // objA4
      refA1 = objA4;
      refA1.Print(); // A2.Print - no polymorphism
      (refA1 as A4).Print(); // A4.Print - static polymorphism
    }
  }
}

Figure 4 schematically illustrates a call to the Print() method when using the new keyword.

C#. Inheritance. The hierarchy of classes. Open circuit of virtual methods in class

Figure 4. The hierarchy of classes A1, A2, A3, A4. Open circuit of virtual Print() methods in class A3

The result of the program

A1.Print()
A2.Print()
A2.Print()
A3.Print()
A2.Print()
A4.Print()

 

8. Example of classes Figure->Rectangle->RectangleColor, that demonstrates a polymorphism

Declare a Figure class containing a name field that defines the name of the figure. In the Figure class, declare the following methods:

  • constructor with one parameter;
  • method Display(), that shows a name of figure.

From the Figure class, inherit the Rectangle class (rectangle), which contains the following fields:

  • coordinate of the upper left corner (x1; y1);
  • coordinate of the lower right corner (x2; y2).

In the Rectangle class, implement the following methods and functions:

  • a constructor with 5 parameters that calls the constructor of the base class Figure;
  • constructor without parameters, which implements the setting of the coordinates of the angles (0; 0), (1; 1) and calls the constructor with 5 parameters using the this tool;
  • method Display() that displays the name of the shape and the values of the internal fields. This method refers to the base class method of the same name;
  • method Area(), which returns the area of a rectangle.

From the Rectangle class, inherit the RectangleColor class. In the RectangleColor class, implement the color field and the following methods:

  • a constructor with 6 parameters, which calls the constructor of the base class Rectangle;
  • constructor without parameters, which sets the coordinates (0; 0), (1; 1) and calls the constructor with 6 parameters using this tool;
  • method Display() that displays the name of the figure and the values of the internal fields. This method refers to the base class method of the same name;
  • method Area(), which returns the area of the rectangle. The method calls the Area() method of the base class.

In the main() function, do the following:

  • declare a reference to the base class Figure;
  • create instances of the Rectangle and RectangleColor classes;
  • demonstrate the use of dynamic polymorphism to access methods of derived classes by referencing the Figure class.

The text of the program is as follows:

using System;
using static System.Console;

namespace ConsoleApp1
{
  // Class Figure
  class Figure
  {
    // Hidden class field
    protected string name;

    // Constructor with 1 parameter
    public Figure(string name) { this.name = name; }

    // Virtual method Display() - uses 'virtual' keyword
    virtual public void Display()
    {
      WriteLine("Figure.name = {0}", name);
    }
  }

  // Rectangle class - inherits (extends) the capabilities of the Figure class
  class Rectangle : Figure
  {
    // Hidden fields - coordinates of points
    protected double x1, y1, x2, y2;

    // Constructor with 5 parameters
    public Rectangle(string name, double x1, double y1, double x2, double y2) :
        base(name) // call the base class constructor
    {
      this.x1 = x1; this.y1 = y1;
      this.x2 = x2; this.y2 = y2;
    }

    // Constructor without parameters, calls the constructor with 5 parameters
    public Rectangle() : this("Rectangle", 0, 0, 1, 1) { }

    // Method Display() - overrides the same method of the base class,
    // therefore, to ensure polymorphism,
    // the override keyword must be specified.
    public override void Display()
    {
      base.Display(); // invoke method Display() of base class
      Write("Rectangle: x1 = {0:f2}, y1 = {1:f2}, ", x1, y2);
      WriteLine("x2 = {0:f2}, y2 = {1:f2}", x2, y2);
    }

    // Method that returns the area of a rectangle
    public double Area()
    {
      return Math.Abs(x1 - x2) * Math.Abs(y1 - y2);
    }
  }

  // RectangleColor class - adds color to the gadget,
  // inherits the capabilities of the Rectangle class
  class RectangleColor : Rectangle
  {
    // Hidden field of the class
    protected int color = 0;

    // Constructor with 6 parameters,
    // calls the constructor of base class Rectangle
    public RectangleColor(string name, double x1, double x2,
        double y1, double y2, int color) : base(name, x1, y1, x2, y2)
    {
      this.color = color;
    }

    // Constructor without parameters,
    // invokes the constructor with 6 parameters
    public RectangleColor() : this("RectangleColor", 0, 0, 1, 1, 0) 
    { }

    // Method Display() - calls the base class method of the same name;
    // to ensure polymorphism, specify override
    public override void Display()
    {
      base.Display();
      WriteLine("RectangleColor.color = {0}", color);
    }

    // Method for calculating the area
    public new double Area()
    {
      return base.Area(); // invoke the method Area() of base class
    }
  }

  class Program
  {
    // A method demonstrating polymorphism.
    // A reference to the base class Figure is passed to the method
    static void DemoPolymorphism(Figure refFg)
    {
      // Invoke the method Display()
      refFg.Display(); // One interface - different implementations
    }

    static void Main(string[] args)
    {
      // 1. Declare a reference to base class
      Figure refFg;

      // 2. Create instances of Figure, Rectangle, RectangleColor classes
      Figure objFg = new Figure("Figure");
      Rectangle objRect = new Rectangle("Rectangle", 1, 2, 5, -4);
      RectangleColor objRectCol = new RectangleColor("RectangleColor", 1, 8, -1, 3, 2);

      // 3. Demonstrating polymorphism using a reference to a base class
      // 3.1. Assign a reference to a base class
      //      the value of a reference to the class Figure
      refFg = objFg;
      refFg.Display(); // invokes Figure.Display()

      // 3.2. Set reference to the value of the reference to the Rectangle
      refFg = objRect;
      refFg.Display(); // Rectangle.Display() called

      // 3.3. Set refFg to objRectCol
      refFg = objRectCol;
      refFg.Display(); // RectangleColor.Display() called

      // 4. Demonstration of polymorphism by passing a parameter to a function
      // 4.1. Passing a reference to a Rectangle class instance in DemoPolymorphism
      refFg = objRect;
      Program.DemoPolymorphism(refFg); // invoke Rectangle.Display()

      // 4.2. Passing a reference to the RectangleColor
      //      class instance in DemoPolymorphism
      refFg = objRectCol;
      Program.DemoPolymorphism(refFg); // invoke RectangleColor.Display()
    }
  }
}

The program result

Figure.name = Figure
Figure.name = Rectangle
Rectangle: x1 = 1.00, y1 = -4.00, x2 = 5.00, y2 = -4.00
Figure.name = RectangleColor
Rectangle: x1 = 1.00, y1 = 3.00, x2 = 8.00, y2 = 3.00
RectangleColor.color = 2
Figure.name = Rectangle
Rectangle: x1 = 1.00, y1 = -4.00, x2 = 5.00, y2 = -4.00
Figure.name = RectangleColor
Rectangle: x1 = 1.00, y1 = 3.00, x2 = 8.00, y2 = 3.00
RectangleColor.color = 2

 


Related topics