Modern programming languages such as C# are object oriented in nature. The C# language allows you to think and program in terms of classes and objects. In this section will discuss features of object-oriented programming such as classes, objects, abstraction, encapsulation, inheritance, interfaces and polymorphism.
CLASSES AND OBJECTS:
Classes and objects are used everywhere in C# and .NET Framework. so what is a class? simply put, a class is a blueprint or template for creating a type. A class typically groups data and behavior of a type. some real world examples of classes are Customers, Orders etc.
An object is a particular instance of class. for example an employee with ID 1234 is an instance of the Employee class.
In C# Classes and objects are created as shown here:
public class Employee
{
// Fields, properties, methods and events go here…
}
You use “class” keyword to define a class, a class contains properties, methods, events. Once created, a class can be instantiated by using the new keyword.
Employee obj = new Employee();
Employee is a class and obj is an instance of the Employee class.
ABSTRACTION:
The process of filtering the available information and arriving at a subset that is essential for your application is called abstraction.
Let’s see the process of abstraction in object – oriented programming, suppose you are building a business-contact management application. Based on your software requirements, you need a Person class in your application. see below figure. left side shows the plethora of detail information about a person, right side shows the essential information.
So your application needs only essential information, but other personal details such as number,relatives etc are irrelevant to your application. Thus, while creating the Person class, you will skip these unwanted piece of information.
Abstraction is a process of filtering out unwanted pieces of information and picking what is essential to your application based on given requirements or context.
ENCAPSULATION:
Encapsulation is a process by which you both bundle data and operations (or functions) that work on that data into a class and also establish a prescribed way to read, write, and process the data.
Let’s see an example on how to achieve encapsulation after abstraction:
Encapsulating Contact Data into a Contact Class.
public class Contact
{
private string fname;
private string lname;
private string emailaddr;
private string phoneno;
private string addr;
private string cname;
private byte[] photo;
private DateTime dob;
public string FirstName
{
get { return fname; }
set { fname = value; }
}
public string LastName
{
get { return lname; }
set { lname = value; }
}
public string EmailAddress
{
get { return emailaddr; }
set
{
if(emailaddr.Contains("@") && emailaddr.Contains("."))
{
emailaddr = value;
}
else
{
throw new Exception("Invalid Email address!");
}
}
}
public string PhoneNo
{
get { return phoneno; }
set { phoneno = value; }
}
public string Address
{
get { return addr; }
set { addr = value; }
}
public string CompanyName
{
get { return cname; }
set { cname = value; }
}
public byte[] Photo
{
get { return photo; }
set { photo = value; }
}
public DateTime BirthDate
{
get { return dob; }
set
{
if ((DateTime.Today.Year - value.Year) > 18)
{
dob = value;
}
else
{
throw new Exception("Invalid birth date. Age must be greater than 18.");
}
}
}
}
The Contact class stores contact information in private variables. These variables are hidden from external world. This way data cannot be tampered with directly from code external to the Contact class.Access to these private variable only granted thru public properties.Notice how the EmailAddress and BirthDate properties perform validations on the values being assigned to the respective properties. They reject invalid values by throwing exceptions, the point here is that you have not only bundled data in a class but also established a prescribed way to access the data. That’s what Encapsulation is about.
As you can see from figure, you have put a “fence” of properties over your data, thus hiding the data from the external world and restricting how the data can be accessed. You can also use methods to encapsulate data in a similar fashion, depending on the requirements.
INHERITANCE:
Inheritance allows you to isolate common pieces of data and operations into a class and then create specialized classes based on that class. The class containing common data and operations is called the base class, whereas the specialized classes are called derived classes.The derived class and the base class are related to each other through an “is-a” relationship.
This means a derived class (say, BusinessContact) is a kind of base class (say, Contact). The derived classes can add data and operations that are specific to themselves. They can also redefine the operations defined by the base class.
Let’s understand base class and derived class with an example.
public class Contact
{
private string fname;
private string lname;
private string emailaddr;
private string phoneno;
private string addr;
private string cname;
private byte[] photo;
private DateTime dob;
public string FirstName
{
get { return fname; }
set { fname = value; }
}
public string LastName
{
get { return lname; }
set { lname = value; }
}
public string EmailAddress
{
get { return emailaddr; }
set
{
if (emailaddr.Contains("@") && emailaddr.Contains("."))
{
emailaddr = value;
}
else
{
throw new Exception("Invalid Email address!");
}
}
}
public string PhoneNo
{
get { return phoneno; }
set { phoneno = value; }
}
public string Address
{
get { return addr; }
set { addr = value; }
}
public string CompanyName
{
get { return cname; }
set { cname = value; }
}
public byte[] Photo
{
get { return photo; }
set { photo = value; }
}
public DateTime BirthDate
{
get { return dob; }
set
{
if ((DateTime.Today.Year - value.Year) > 18)
{
dob = value;
}
else
{
throw new Exception("Invalid birth date. Age must be greater than 18.");
}
}
}
}
public class BusinessContact1 : Contact
{
private string businessname
public string BusinessName
{
get { return businessname; }
set { businessname = value; }
}
}
ABSTRACT CLASSES AND INTERFACES:
In the above section you used inheritance as a means to reusing code. There are time when classes can have a parent – child relationship, but there can’t be any possibility of code reuse.Consider that you are building a taxation system that is supposed to calculate taxes based on certain logic. In addition, let’s suppose that your application needs to support tax calculation in three countries—say the USA, the UK, and Canada. Now the taxation rules and logic in a country are usually quite specific to that country, and you may not be able to reuse any of this code. However, the operation—calculating tax—is needed in all of them. In such cases, although you can’t reuse any code, you can reuse “contract.” In such cases, developers resort to abstract classes or interfaces.
Characteristics of abstract classes:
1. An abstract class is a class that cannot be instantiated.
2. Class must be declared as abstract.
3. Use abstract classes when you need to provide some functionality but also wish to defer some implementation.
4. The purpose of an abstract class is to provide a common definition of a base class that multiple derived classes can share.
Abstract classes usually contains property and method signatures but no implementation. The derived classes are required to write the implementation of these members by overriding them. here is an example of abstract class.
public abstract class CountryTaxCalculator
{
public abstract decimal CalculateTaxAmount();
}
public class TaxCalculatorForUS : CountryTaxCalculator
{
public override decimal CalculateTaxAmount()
{
// implement Calculate Tax for USA specific
}
}
public class TaxCalculatorForUK : CountryTaxCalculator
{
public override decimal CalculateTaxAmount()
{
// implement Calculate Tax for UK specific
}
}
Another example of Abstract and Concrete class:
In the above diagram there is an abstract class called Employee. In UML, the name of an abstract class is written in an italic font. This class contains one abstract method called calculatePay, it is written in a italic font. An abstract method has no implementation. Typically an abstract class contains one or more abstract method. The diagram also shows three subclasses that inherit behaviour and data attributes from the Employee class. These are concrete classes that can be instantiated; abstract classes cannot directly be instantiated. Think of abstract classes are providing some generic behaviour but also delegating some behaviour to a subclass. The subclass will have to provide the delegated behaviour or it will also have to be marked as abstract and hence will not be able to be instantiated.
Key point: When extending an abstract class the subclass must implement all of the abstract methods in order to be classified as a concrete class.
C# also allows you to create interfaces that serve a similar purpose. Unlike abstract classes, which can contain code of their own, interfaces simply provide a set of property and method signatures with no implementation code at all. Classes then implement one or more interfaces depending on the requirements.
Implementing an Interface:
public interface ICountryTaxCalculator
{
decimal CalculateTaxAmount();
}
public class TaxCalculatorForUS : ICountryTaxCalculator
{
public decimal CalculateTaxAmount()
{
//Implement the CalculateTaxAmount method here
}
}
public class TaxCalculatorForUK : ICountryTaxCalculator
{
public decimal CalculateTaxAmount()
{
//Implement the CalculateTaxAmount method here
}
}
Key point: A class can implement multiple interfaces, The interface provides no implementation details.
It is suffice to say that both (Abstract class & Interface) allow us to define contracts of properties and methods that are then implemented by other classes.
POLYMORPHISM:
Polymorphism means multiple forms of something. Let’s say you own a washing machine and a vacuum cleaner. Both are machines and can be started and stopped using their respective switches. However, your order to start and stop a machine is obeyed quite differently by the respective machines. And it also results in different results. A washing machine, when started, is going to wash clothes, whereas a vacuum cleaner is going to clean the floor when started. Thus, the same order—“start”—has two different results based on the machine to which it has been issued. The same can be said about the “stop” instructions.
How does this apply to classes? Imagine the preceding real-world example in terms of classes. The WashingMachine and VacuumCleaner classes bear an “is-a” relationship with the Machine class. The Machine class, the WashingMachine class, and the VacuumCleaner class have a Start() method. But each Start() implementation would be different for obvious reasons. Thus the same method—Start()—has multiple forms. Polymorphism comes in different flavors, such as operator overloading, method overloading, polymorphism via inheritance, and polymorphism via interfaces.
Polymorphic Behavior Through Inheritance
Suppose we are building our contact management application and decide to have four classes named Contact, BusinessContact, ProfessionalContact, and PersonalContact. For the sake of this example, let’s assume that you are writing the following code in a Console Application project. The Contact class is the base class for the remaining three classes and is shown here:
Contact base class:
// Base class
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string PhoneNo { get; set; }
public virtual string GetDetails()
{
return FirstName + " " + LastName + " (" + EmailAddress + "," + PhoneNo + ")";
}
}
The Contact class consists of four properties (FirstName, LastName, EmailAddress, PhoneNo) and a method (GetDetails). The properties and the method are quite straightforward. What’s worth noting is that GetDetails() is marked as a virtual method. This means you can override it in the derived classes. The GetDetails() method simply returns FirstName, LastName, EmailAddress, and PhoneNo to its caller for formatting.
Next, the BusinessContact class inherits from the Contact class and looks as shown here:
//BusinessContact class inherits from the Contact class
public class BusinessContact : Contact
{
public string CompanyName { get; set; }
public string Designation { get; set; }
public override string GetDetails()
{
return FirstName + " " + LastName + " (" + Designation + ", " + CompanyName + ")";
}
}
As you can see, the BusinessContact class adds two more properties (CompanyName and Designation). It also overrides the GetDetails() method and redefines it to return FirstName, LastName, Designation, and CompanyName to the caller.
Create ProfessionalContact & PersonalContact classes.
//ProfessionalContact classe that inherit Contact
public class ProfessionalContact : Contact
{
public string Service { get; set; }
public string Address { get; set; }
public string Timing { get; set; }
public override string GetDetails()
{
return FirstName + " " + LastName + " (" + Service + ", " + Timing + ")";
}
}
//PersonalContact classe that inherit Contact
public class PersonalContact : Contact
{
public string Address { get; set; }
public DateTime BirthDate { get; set; }
public override string GetDetails()
{
return FirstName + " " + LastName + " (" + BirthDate.ToString("dd-MMM-yyyy") + ")";
}
}
Now that you have all four classes ready, let’s observe their polymorphic behavior. Add a method to the Program class (the class that houses the Main() method) as shown here:
Now, add the following code inside the Main() method:
BusinessContact c1 = new BusinessContact();
c1.FirstName = "Nancy";
c1.LastName = "Davolio";
c1.EmailAddress = "nancy@localhost";
c1.PhoneNo = "(206) 555-9857";
c1.CompanyName = "Northwind Traders Inc.";
c1.Designation = "Sales Representative";
ProfessionalContact c2 = new ProfessionalContact();
c2.FirstName = "Andrew";
c2.LastName = "Fuller";
c2.EmailAddress = "andrew@localhost";
c2.PhoneNo = "(206) 555-9482";
c2.Service = "Doctor";
c2.Address = "908 W. Capital Way, Tacoma, USA";
c2.Timing = "10 AM to 6 PM";
PersonalContact c3 = new PersonalContact();
c3.FirstName = "Janet";
c3.LastName = "Leverling";
c3.EmailAddress = "janet@localhost";
c3.PhoneNo = "(206) 555-3412";
c3.BirthDate = new DateTime(1971, 3, 20);
ShowDetails(c1);
ShowDetails(c2);
ShowDetails(c3);
public static void ShowDetails(Contact c)
{
string details = c.GetDetails();
Console.WriteLine(details);
Console.ReadLine();
}
The preceding code basically instantiates BusinessContact, ProfessionalContact, and PersonalContact classes. It also sets various properties of these objects. Finally, it calls the static ShowDetails() method by passing the objects just created. Notice that the ShowDetails() method takes a parameter of type Contact, but we are passing objects of type BusinessContact, ProfessionalContact, and PersonalContact, respectively. How is that possible? That’s because all these classes are derived from the Contact class. Thus, objects of a derived type can be used where the base type is expected. What is more interesting is that a run of the application will reveal that although ShowDetails() is invoking GetDetails() on a Contact class, it is the GetDetails() method of the individual derived type that gets executed. Figure 1-3 shows a test run of the application.
As you can see, when you pass an object of type BusinessContact, GetDetails() of BusinessContact is getting executed and not GetDetails() of Contact. This is because the base class method is marked as virtual and the derived class overrides it. Similar behavior can be seen from ProfessionalContact and PersonalContact objects. This is polymorphism! The same method, GetDetails(), behaves differently based on the underlying object.
Just for the sake of testing, remove the override keyword from all the GetDetails() methods for the derived classes. Run the application again.
Effect of removing virtual and override keywords
What happens? All the calls to GetDetails() now execute the base class version of the method and return only FirstName, LastName, EmailAddress, and PhoneNo. No more polymorphic behavior! That’s the significance of virtual and override keywords.
Polymorphic Behavior Through Interfaces:
Now that you know how to achieve polymorphic behavior through inheritance, let’s shift the focus to polymorphic behavior through interfaces . As an example, let’s take the same scenario of the tax calculation application that we discussed while learning about interfaces. You can code this example as a Console Application project.
interface ICountryTaxCalculator
{
decimal CalculateTaxAmount();
}
// implemented ICountryTaxCalculator
public class TaxCalculatorForUS : ICountryTaxCalculator
{
public decimal CalculateTaxAmount()
{
return 10000m;
}
}
// implemented ICountryTaxCalculator
public class TaxCalculatorForUK : ICountryTaxCalculator
{
public decimal CalculateTaxAmount()
{
return 20000m;
}
}
ICountryTaxCalculator Being Implemented in Tow Classes.
As you can see, the ICountryTaxCalculator interface defines a method—CalculateTaxAmount()—that returns the tax amount as a decimal. ICountryTaxCalculator is implemented by three classes—TaxCalculatorForUS, TaxCalculatorForUK, and TaxCalculatorForIN. Each of these classes implements the CalculateTaxAmount() method and returns some arbitrary test values to the caller.
The Program class has a ShowDetails() method for displaying tax details. ShowDetails() is shown here:
static void Main(string[] args)
{
TaxCalculatorForUS t1 = new TaxCalculatorForUS();
TaxCalculatorForUK t2 = new TaxCalculatorForUK();
ShowDetails(t1);
ShowDetails(t2);
}
public static void ShowDetails(ICountryTaxCalculator t)
{
decimal tax = t.CalculateTaxAmount();
Console.WriteLine("Tax Amount : " + tax);
Console.ReadLine();
}
The Main() method creates three objects of types TaxCalculatorForUS, TaxCalculatorForUK, and TaxCalculatorForIN, respectively. ShowDetails() is then called by passing these objects as parameters. So, this time the parameter is an interface type and you are able to pass any object that implements that interface.
Polymorphic behavior through interfaces
This same method, CalculateTaxAmount(), behaves differently, returning different tax amounts depending on the underlying object. This is polymorphic behavior through interfaces.