Inheritance

An important feature of the object-oriented paradigm is the ability to specify a common functionality for different classes and implement it inside one class. All classes (descendants) that should share this functionality will inherit from that class (base class). We may also say that descendant classes extend a base class, increasing our code reusability and reducing the possibility of errors (useful for debugging).

PerlNET provides a convenient and easy-to-use way to inherit from .NET classes. In this section, you will find all the required information for implementing inheritance.

System.Object

In the previous section, we stated that only .NET types in PerlNET may extend other .NET classes. The System.Object class is kind of an exception to this rule. Every .NET class must inherit from System.Object, and Pure Perl classes must therefore inherit from System.Object as well. Moreover, even an executable PerlNET program is wrapped by a .NET class, which inherits from System.Object. We demonstrate with the following sample:


#
# Simplest.pl
#
print "The Simplest Program
";

We may compile this Perl program with the plc program and convert it into .NET assembly. However, there is not even the tiniest hint of extending some class in this one line of code.

To illustrate what we get upon compiling our program, we use ILDASM tool that is provided with the .NET Framework SDK. It takes us inside our assemblies, revealing their structure. First, compile Simplest.pl and then run the tool from the command line—type ildasm. You should see an empty window with a menu bar. Choose File | Open and then browse to Simplest.exe in the Simplest folder under this chapter's samples directory. You should see the window illustrated in Figure 12-1.

Figure 12-1. ILDASM shows an assembly created by PerlNET.


The square with three lines denotes a class inside our assembly. We clearly see that our assembly exposes two classes, although we did not define either of them in our simple program.

The __perl class is generated by the Perl compiler and is added to every assembly that originates from PerlNET code. This class helps to interpret the code of our program or component by communicating with the PerlNET run-time modules.

The __Main class was automatically generated for our program by plc. We may expand its branch and look inside, as illustrated in Figure 12-2.

Figure 12-2. Looking inside the __Main class.


The first item in the __Main class, with the triangle from the left, is the class manifest. We can double-click this item to get more information about the class. If we do so, then a new window pops up with the following text:

.class public auto ansi beforefieldinit __Main
       extends [mscorlib]System.Object
{
} // end of class __Main

The second line of the class manifest states that our __Main class extends System.Object. Recall that we did not declare a class and there was no word about inheritance or extending in our simple program. Thus, the Perl compiler does this automatically. The same thing (inheriting from System.Object) occurs with every PerlNET component we compile with plc.

Besides the manifest, we see two other entries under the __Main branch. The first is a constructor that is called internally by the CLR. The second is the Main method. As previously stated, every .NET executable assembly contains a class with the Main method. Either we may define this method explicitly or plc will do it for us in addition to wrapping our simple program by a .NET class. The Main method is an entry point to our program. The CLR looks inside the executable assembly for the class with this method and calls it. If no such method is found, an error occurs. That is why the PerlNET compiler generates for us a class with the public Main method.

Deriving from .NET Classes

We just saw the demonstration of implicit inheritance from System.Object. Now, we will learn how to inherit from any non-sealed .NET class using PerlNET.

EXTENDS ATTRIBUTE

We would like to extend functionality of StockItem and write a new class named BookItem, which will represent any book in our stock. To inherit from StockItem, we have to use the [extends: …] attribute in our class interface definition for the BookItem class.

package OI::Samples::BookItem;

# Using extends attribute
=for interface
   [extends: StockItem]
=cut
# Other interface definitions and subroutines
. . .

The .NET environment does not support multiple inheritance, which means that we can extend one class only. Therefore, the [extends: …] attribute should appear at most once in our PerlNET class interface definition.

CREATING INHERITED COMPONENTS

Inheriting from other .NET classes gives us an option to use all the functions that were defined in the base class. However, we should perform some additional work. One of our tasks is to define a constructor for our new class, and there are no innovations in this—we do it the same way as we did for all our classes. Our package—from the BookItemStep1 folder—looks like this:


package OI::Samples::BookItem;

=for interface
   [extends: StockItem]
   static BookItem(int id, str name);
=cut

# Constructor subroutine
sub BookItem
								{
								my($this, $id, $name) = @_;
								$this->{f_ID} = $id;
								$this->{f_Name} = $name;
								}
							

This package does not give any extended functionality, but we will study it for a while and enrich its interface step by step. For now, we just copy the functionality of StockItem. When building the package with plc, we have to reference the DLL where the base class resides.

plc BookItem.pm -target="library"
–reference="stockitem.dll"

Running the above command results in the following error:

error CS1501: No overload for method 'StockItem' takes '0'
 arguments

This actually means that our StockItem class interface was not properly defined to serve as a base class for other PerlNET classes. Other .NET-compliant languages will inherit successfully from StockItem, but PerlNET requires that any base class defines a default constructor—a constructor with no parameters. Otherwise, we cannot extend the class in PerlNET. As a result, we have to perform modifications to StockItem and overload the constructor.

# Constructor
=for interface
   # Default constructor
   static StockItem();
   static StockItem(int id, str name);
=cut

Additionally, the implementations of the StockItem subroutine should be corrected.

sub StockItem
{
   # Get REFERENCE to the object
   my $this = shift;
   my ($id, $name) = @_;
   if (defined $id)
   {
      $this->{f_ID} = $id;
      $this->{f_Name} = $name;
   }
   else
								{
								# No arguments supplied – Default Constructor
								$this->{f_ID} = -1;
								$this->{f_Name} = "No Name";
								}
}

After these modifications to StockItem and rebuilding of StockItem.dll, we can successfully build BookItem.dll. However, as we mentioned before, some errors appear only at runtime. We wrote a small client program (BookItemCli) for the BookItem class.


#
# BookItemCli.pl
#
use strict;
use namespace "OI.Samples";
use PerlNET qw(AUTOCALL);

my $book = BookItem->new(1, "PerlNET book");
$book->OutputItem(PerlNET::true);

BookItemCli.exe is built successfully, but when running it, we get an error:

System.ApplicationException: Can't locate any property or field called 'f_ID' for OI
.Samples.BookItem ...

What did we do wrong this time? As you can see, we accessed the f_ID and f_Name fields without defining them in the BookItem class.

sub BookItem
{
   my($this, $id, $name) = @_;

   $this->{f_ID} = $id;
								$this->{f_Name} = $name;
}

The inheritance principles tell us that we should be able to work with a base class interface. Our mistake was accessing two fields that are private to StockItem and hence not visible outside of this class. To keep these fields invisible to the StockItem class users on the one hand and to allow descendant classes to work with these fields on the other hand, we should apply the protected modifier to our fields in the interface definition of the base class.

package OI::Samples::StockItem;
. . . . . . . . . . . . . . . .
# Fields
=for interface
  protected field int f_ID;
  protected field str f_Name;
=cut

This is enough to rebuild StockItem.dll and rerun BookItemCli.exe. Finally, we get clean execution with the following output:

---------------------------
1 PerlNET book
---------------------------

We placed the fixed version of the StockItem class in the StockItemStep2 directory.

OVERRIDING BASE CLASS METHODS

Many times when inheriting from some class, we would like to provide our own implementation for some base class methods or override methods. PerlNET has an appropriate method modifier we should apply inside interface definition of the descendant class. We should specify the method prototype we are going to override and apply the override modifier to the method.


=for interface
   override BaseClassMethodPrototype;
=cut

However, we cannot override every base class method, as such methods should have one of the following modifiers in the base class:

  • abstract

  • virtual

  • override

Applying the abstract modifier is analogous to defining pure virtual function in C++. We do not implement this function, and all descendants must override it. With the virtual modifier, we instruct PerlNET that this function may be overridden in the inherited classes. Finally, the presence of the override modifier in the base class means that our base class provides its definition of the method from its base class. The override modifier has the same effect as virtual.

We may decide that the BookItem class should have its own OutputItem method implementation. Therefore, first we change the StockItem class by applying virtual modifier to the OutputItem method (StockItemStep3). Then, we can make the following addition to the BookItem class package in the interface definition (BookItemStep2):



=for interface
   override void OutputItem(bool lines);
=cut

The following subroutine implements the new version of OutputItem:

sub OutputItem
{
   my($this, $lines) = @_;
   if (bool($lines))
   {
      print "---------------------------
";
   }
   print "Item type: Book
";
   print $this->{f_ID}, " ", $this->{f_Name}, "
";
   if (bool($lines))
   {
      print "---------------------------
";
   }
}

This way, whenever a class user invokes the OutputItem method on the BookItem object, the new version presented above will be called.

INITIALIZATION OF EXTENDED CLASS

You can see that despite inheriting all the base class functionality, we had to initialize our fields in the BookItem class. This is not critical if we do just simple and not numerous initializations. However, it will be time and code consuming to rewrite the initialization code in each inherited class. Therefore, we need a way to eliminate these inconveniences as much as possible.

There two types of object initialization:

  • Default

  • Parametric

For each of these types, we suggest our solution to handle the problems mentioned above.

DEFAULT INITIALIZATION

Default initialization is used when all instances of a class get the same initial properties—the same state. We may assign property and field values in a default constructor that takes no parameters. This constructor works for descendant classes too, as the PerlNET runtime module automatically invokes the default constructor whenever we create the inherited class object. To demonstrate default initialization, we wrote two classes:

  • Circle is the base class.

  • RandCircle extends Circle.

The Circle class has one read-write Radius property and one method, CalculateArea. We have defined one default constructor. It initially sets the value of Radius to 5. After the instance is created, we may get or set Radius of a circle and calculate its area with the CalculateArea method. Here is the class code, which is located in the Circle folder.


#
# Circle.pm
#
package OI::Samples::Circle;
use PerlNET qw(AUTOCALL);
use strict;

# Default constructor
=for interface
   static Circle();
=cut

# Calculate Area Method
=for interface
   num CalculateArea();
=cut

# Radius Property
=for interface
   int Radius;
=cut

# Field to store property value
=for interface
   protected field int f_Radius;
=cut

# Default constructor implementation
sub Circle
{
   my $this = shift;

   # Initialize field that stores property value
   $this->{f_Radius} = int(5);
}

# CalculateArea implementation
sub CalculateArea
{
   my $this = shift;
   return("System.Math"->PI*($this->{f_Radius}**2));
}

# Radius Property implementation
sub Radius
{
   my ($this, $value) = @_;
   if (defined $value)
   {
      # Set radius
      $this->{f_Radius} = $value;
   }
   else
   {
      return $this->{f_Radius};
   }
}

Now, we would like to implement the descendant RandCircle class, which will extend the Circle class functionality with one method, RandRadius. This method will generate a new circle radius value randomly and will assign it to the Radius property.

Note that we do not need to initialize the Radius property in the descendant class, as the default constructor of the base class will do this job for us. Here is the code for RandCircle.


#
# RandCircle.pm
#
package OI::Samples::RandCircle;

use namespace "OI::Samples";
use strict;

=for interface
   [extends: Circle]
								# We do no have to specify the default
								# constructor – PerlNET will generate one
=cut

# Random radius generating
=for interface
   void RandRadius();
=cut

# No need to implement the default constructor
# The Radius property is initialized in the base class

# Random Radius implementation
sub RandRadius
{
  my $this = shift;
  $this->{f_Radius} = int(rand(20) + 1);
}

To test RandCircle, we wrote a simple client program that resides in the RandCircleCli directory. Here is the code and the output of this program.


#
# RandCircleCli.pl
#
use strict;
use namespace "OI.Samples";

$| = 1;
my $circle = RandCircle->new();
print "Radius: ", $circle->{Radius}, "
";
print "Area: ", $circle->CalculateArea(), "
";
print "----------------------------
";
$circle->RandRadius();
print "New Radius: ", $circle->{Radius}, "
";
print "New Area: ", $circle->CalculateArea(), "
";

Output:

Radius: 5
Area: 78.5398163397448
----------------------------
New Radius: 9
New Area: 254.469004940773

As you can see, the default constructor of the base class initialized the Radius property of the descendant class automatically. The PerlNET runtime module always invokes the base class constructor before the descendant class constructor.

PARAMETRIC INITIALIZATION

Programmers commonly use parametric initialization. It allows other developers to pass some parameters to a class constructor, and in this way to set the initial state of a new object. Initialization occurs inside the constructor subroutine. When we create an object of the inherited class, then we pass some parameters that should be assigned to properties of the base class and others for inherited class properties. The best way to handle this is to call the base class constructor and supply it with appropriate parameters. Unfortunately, there is no special syntax in PerlNET to call the base class constructor.

However, we suggest a solution to this problem. Recall that in the BookItem class we defined previously in this chapter, we had to manually initialize two fields, f_ID and f_Name. We were not able to call the second version of the base class constructor that gets id and name parameters, because PerlNET does not allow this. That is why we suggest defining in the base class an additional method. We will call it _init. This method will replicate the functionality of a constructor. It gets the same parameters as the constructor and performs the corresponding assignments. This way, we avoid unnecessary initialization code in inherited classes, as they can call the _init method from their constructor and supply this method with initialization parameters for the base class. There may be several overloaded versions of _init methods as there are a number of nondefault constructors. The base class constructors may call the _init method too for the sake of code accuracy. It is reasonable to use such a solution whenever we have more then one property or field to initialize.

Going back to our items classes, we should slightly change an implementation of the StockItem class to reflect the proposed specification from the previous paragraph. We present here only the changed code fragments. The full code version may be found in the StockItemStep4 directory.


#
# StockItem.pm
#
package OI::Samples::StockItem;

use strict;
use PerlNET qw(bool);

# Constructor
=for interface
   # No [interface: pure] attribute
   static StockItem();
   static StockItem(int id, str name);
=cut

# Initializer method same params as second constructor
=for interface
   protected void _init(int id, str name);
=cut

. . .

# Constructor definition
sub StockItem
{
   # Get REFERENCE to the object
   my $this = shift;
   my ($id, $name) = @_;
   if (defined $id)
   {
      $this->_init(int($id), $name);
   }
   else
   {
      $this->_init(int(-1), "No Name");
   }
}

# Initializer implementation
sub _init
								{
								my ($this, $id, $name) = @_;
								$this->{f_ID} = $id;
								$this->{f_Name} = $name;
}

The BookItem class constructor implementation will change accordingly and will become more accurate. Here is the new code (located in BookItemStep3).


#
# BookItem.pm
#
package OI::Samples::BookItem;
use namespace "OI.Samples";
use strict;

# Constructor
=for interface
   [extends: StockItem]
   static BookItem(int id, str name);
=cut

# Constructor implementation
sub BookItem
{
   my($this, $id, $name) = @_;

   $this->_init($id, $name);
}

INHERITANCE SUMMARY

As we have just shown, we must think ahead when we develop a set of classes that implement inheritance principles. Let's review the main points that you should consider when designing base and descendant classes.

Any class that you are going to use as a base class for other PerlNET classes must have the default constructor. Any property, field, or method that you want to hide from class users but allow access for descendant classes should have the protected modifier inside an interface definition. It is preferable that common initialization code for all class instances reside inside the default constructor. If our class provides nondefault constructors and this class should serve as a base class, then the best way to avoid unnecessary initialization code in descendant classes is to define the protected _init method. This method would get some parameters to assign as initial values to properties (fields). This way, we can call this method from inherited classes and work around the inability to call nondefault constructors of the base class. If you are going to use your class as a base for C# classes, then you do not have to define the _init method, as C# has a special syntax for calling nondefault constructors of the base class. Consult Appendix B for more details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.151.126