Learn Object-Oriented Programming with C#

Learn Object-Oriented Programming with C#

Object-oriented programming is a way of developing software applications using real-world terminologies to create entities (classes) that interact with one another using objects. Learn object-oriented programming with C# using simple tutorials.

OOP Fundamentals

  1. What is Object-Oriented Programming?
  2. Abstraction
  3. Encapsulation
  4. Association, Composition & Aggregation
  5. Inheritance
  6. Polymorphism
  7. Method Hiding in C#

1. Object Oriented Programming in C#

Object-oriented programming is a way of developing software applications using real-world terminologies to create entities that interact with one another using objects.

Object-oriented programming makes applications flexible (easy to change or add new features), reusable, well-structured, and easy to debug and test.

Most programming languages provide the following basic building blocks to build object-oriented applications:

  • Classes: A Class define the structure using methods and properties/fields that resemble real-world entity.
  • Methods: A method represents a particular behavior. It performs some action and might return information about an object, or update an object’s data.
  • Properties: Properties hold the data temporarily during the execution of an application.
  • Objects: Objects are instances of the class that holds different data in properties/fields and can interact with other objects.
  • Interfaces: An interface is a contract that defines the set of rules for a particular functionality. They are used effectively with classes using OOP principles like inheritance and polymorphism to make applications more flexible.

Object-oriented Design Principles

There are various object-oriented principles and techniques using which you can develop applications that are maintainable and extendable.

The followings are four main principles of object-oriented programming:

  1. Abstraction
  2. Encapsulation
  3. Inheritance
  4. Polymorphism

You will learn the above principles in detail below.


Steps for Developing Object-oriented Applications

Developing an object-oriented application starts with the business requirement document. Typically, a business analyst provides you with a business requirement document after understanding and analyzing the requirement from the customer. So, the business requirement is the starting point.

The followings are overall steps to develop an object-oriented application:

  1. Abstraction: First, identify essential entities and their characteristic from the business requirement for a high-level view.
    • Find nouns from the business requirement (the noun is the person, place, thing, or process).
    • Identify potential classes and their members from the nouns.
  2. Encapsulation: An implementation of abstraction in code. Create classes and their members with appropriate access modifiers to show functionalities and hide details and complexity.
  3. Define relationship: Establish relationships between classes using inheritance and polymorphism.
    • Inheritance
    • Polymorphism
  4. Use Principles & Patterns: Use the SOLID principles and Design Patterns as and when necessary to make applications flexible.
    • Single Responsibility Principle
    • Open/Closed Principle
    • Liskov Substitution Principle
    • Interface Segregation Principle
    • Dependency Inversion Principle

Here you will learn about important object-oriented principles and patterns and when and how to use them in your application.


2. Abstraction in Object-oriented Programming

The dictionary meaning of abstraction is "existing as an idea, feeling, or quality, not as a material object" (Source).

Abstraction is a higher-level concept or a way of thinking when you start designing your application from the business requirement. Abstraction is a process of identifying essential entities (classes) and their characteristics (class members) and leaving irrelevant information from the business requirement to prepare a higher-level application design.

Abstraction starts with identifying nouns in the business requirement or feature request. A noun is a person, place, thing, or process. These nouns can be your entities. After figuring out the required entities you can then collect the relevant characteristics of each entity. Characteristics are information (data) and behaviors (methods) relevant to each entity.

For example, consider the following simple business requirement:

We required a basic learning management system to store student, Course, and teacher data. We should be able to know which student has enrolled for which course and who is the teacher for that course.

In the above business requirement, we identify these nouns: student, course, teacher. These are our entities at the design level which can be implemented as classes.

Now, we can identify essential characteristics for each of these entities which can be implemented as properties or methods in each class. For example, a student has a name, address, and email. They might be doing some extra curriculum activities and they can have different skin colors, religions, etc.

Now, out of this information, we have to pick characteristics that are relevant and essential to our application. So, we can now pick the name, address, and courses he or she enrolled in. We will leave other information as they are not relevant for our application such as skin color, religion, and extra curriculum activities.

Thus, the process of identifying entities and characteristics which can be classes and their members from the business requirement is called "Abstraction".


3. Encapsulation in Object-oriented Programming

People often get confused between abstraction and encapsulation. As we learn in the previous chapter, abstraction is a way of thinking, whereas encapsulation is a technique to implement abstraction.

Encapsulation is a technique to implement abstraction in code. Create classes and their members with appropriate access modifiers to show or hide details and complexity.

Encapsulation hides the data and implementation details show only the required members within a class, thus hiding complexity from other code. No other code needs to know about implementation detail and also can’t modify the code of the class’s data and methods.

Most object-oriented programming languages allow you to create classes and their properties and methods along with the access modifiers such as public, private, protected, and internal to show or hide data members and implementation details. Interfaces and abstract classes can also be used for encapsulation.

For example, the Student class has the following members:

As you can see, the FirstName, MiddleName, LastName, and FullName are data members and Save(), Subscribe(), GetSubscribedCourses() are methods.

In C#, we can implement encapsulation mostly using class, interface, abstract class, property, method, struct, enum, and access modifiers. For the above Student entity, we can create the Student class. Use properties for the data members and methods for the actions.

The following example demonstrates encapsulation.

Example: Encapsulation
public class Student
{
    private string _firstName;

    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    private string _middleName;

    public string MiddleName
    {
        get { return _middleName; }
        set { _middleName = value; }
    }


    private string _lastName;

    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }


    public string FullName
    {
        get { return _firstName + " " + _lastName; }
    }

    public void Save() { 
        //write code to save student 
    }

    public void Subscribe(Course cs)
    {
        Verify();
            
        //write code to subscribe to a course
    }

    private void Verify()
    {
        //write code to verify student before subscribing
    }

    public void GetSubscribedCourses()
    {
        //write code to return all subscribed courses
    }
}

Above, private fields such as _firstName, _middleName, and _lastName store the data privately to hide it from the external code so that they cannot modify it with invalid values. FirstName, MiddleName, and LastName properties use these fields in the getters and setters to return and set values to these fields. These are public properties so that they are visible and accessible to outside code via getters and setters. The FullName property internally uses private variables to return the full name of the student.

In the same way, the public Enroll() method is visible but it hides the implementation detail by internally calling the private Subscribe() method. External code cannot know and access the Subscribe() method because it is a private method.

Advantages of Encapsulation:

  • Hides data and complexities.
  • Restrict unauthorized access of data by allowing authorization before data access.
  • Allow validation before setting data.
  • Only the author of the class needs to understand the implementation, not others.
  • Makes applications easy to maintain.

4. Class Relations: Association and Composition

In object-oriented programming, classes interact with each other to accomplish one or more features of an application. You can define the relationship between classes while designing the classes of your application.

There are three types of relationships in object-oriented programming based on how a class interacts with another class.

  1. Association
  2. Composition
    • Composition
    • Aggregation
  3. Inheritance

The following figure illustrates the relationships.

Class Relationships

Association

Association relationship is referred to as "uses a" relationship where a class uses another class to perform some operation. In association, both classes can exist independently where nobody is the owner of another. Some people refer association as collaboration or delegation.

Association happens between the classes where one class provides a service to another class or the class delegates some kinds of behaviors to another class.

Association is typically implemented with a pointer or reference instance variable or as a method argument.

The following example demonstrates the association relationship between the Student and StudentRepository class.

Example: Association
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
}

public class StudentRepository
{
    public Student GetStudent(int StudentId)
    {
        // get student by id from db here

        return new Student();
    }
    public bool Save(Student student)
    {
        // save student to db here
        Console.WriteLine("Student saved successfully");

        return true;
    }
    public bool Validate(Student student)
    {
        // get student from db to check whether the data is already exist
        Console.WriteLine("Student does not exist.");

        return true;
    }
}

In the above example, the StudentRepository class uses the Student class to save or retrieve student data. Notice that the StudentRepository class uses the Student class as a parameter of methods. The StudentRepository class provides service to any class who is interested in saving or retrieving student data. However, both the classes have independent lifetime meaning that disposing one does not dispose of another. So, we can say that the relationship between the StudentRepository and the Student class is association (or collaboration/delegation). You can also say that the Student class delegates the responsibility of the implementation of saving and retrieving student-related data to the StudentRepository class.

The association relationship between the classes is marked with the arrow in UML diagram, as shown below.

Important Points:

  • A class only uses behaviors/functionalities (methods) of another class but does not change them by overriding them.
  • A class does not inherit another class.
  • A class does not include (own) another class as a public member.
  • Both classes have independent lifetime where disposing of one does not automatically dispose of another.

Composition

Composition is referred to as "has a" relationship. Composition relationship is formed when a class has a reference to another class as an instance property.

In the composition relationships, a class that contains the reference to another class is the parent (owner) of that child class. The child class without parent class doesn't exist.

@*

Composition relationship has two categories:

  1. Composition
  2. Aggregation
*@

For example, the following Student class has a composite relationship with the Address class that holds the student's address. The Address object without the Student object cannot exist.

Example: Composition
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }

    public Address HomeAddress { get; set; }
}

public class Address
{
    public int AddressId { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public string Country { get; set; }

}

In the above example, the Student class has an Address class as a public property which makes the composite relationship between the Student and Address class. If the Student object is deleted then the Address object will also be deleted.

The composite relationship also has a cardinality that is a one-to-one, one-to-many, or many-to-many relationship between classes. In the above example, the Student and the Address class have a one-to-one relationship because each Student will have only one address.

A class can also include the id property of another class instead of an instance to form the composite relationship. For example, the Student class can contain the AddressId property that points to the Address class.

Example: Composition using Id Property
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }

    public int AddressId { get; set; }
}

The composition relationship between the classes is marked using the line in UML diagram, as shown below.

Important Points:

  • A class (parent) contains a reference to another class (child).
  • The child class doesn't exist without the parent class.
  • Deleting the parent class will also delete the child class
  • A class can also include a reference of the id property of another class.

Aggregation

Aggregation is another category of "has a" relationship where a class can contain other classes as properties but those classes can exist independently.

For example, the Student class contains the Course class instance as a property to form the composition relationship. However, both the classes can exist independently and so it is called an aggregation relationship.

Example: Aggregation
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }

    public Course EnrolledCourse { get; set; }
}

public class Course
{
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public IList<string> Topics { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

In the above aggregation relationship, even if the Student object is deleted, the Course object will still exist. The Student class can also contain CourseId property instead of Course instance.

Composition and Aggregation both are "has a" relationship but in the composition relationship, related classes don't exist independently whereas, in the aggregation, related classes exist independently.

The aggregation relationship between the classes is marked with a line in UML diagram, as shown below.

Important Points:

  • Aggregation is another type of composition ("has a" relation).
  • A class (parent) contains a reference to another class (child) where both classes can exist independently.
  • A class can also include a reference of the id property of another class.

5. Inheritance in C#

In object-oriented programming, inheritance is another type of relationship between classes. Inheritance is a mechanism of reusing the functionalities of one class into another related class.

Inheritance is referred to as "is a" relationship. In the real world example, a customer is a person. In the same way, a student is a person and an employee is also a person. They all have some common things, for example, they all have a first name, middle name, and last name. So to translate this into object-oriented programming, we can create the Person class with first name, middle name, and last name properties and inherit the Customer, Student, and Employee classes from the Person class. That way we don't need to create the same properties in all classes and avoid the violation of the DRY (Do not Repeat Yourself) principle.

Note that the inheritance can only be used with related classes where they should have some common behaviors and perfectly substitutable. Follow the Liskov Substitution Principle in inheritance.

Inheritance

In C#, use the : symbol to inherit a class from another class. For example, the following Employee class inherits from the Person class in C#.

Example: Class Inheritance in C#
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string GetFullName(){ 
        return FirstName + " " + LastName;
    }
}

class Employee : Person
{
    public int EmployeeId { get; set; }
    public string CompanyName { get; set; }
    
}

In the above example, the Person class is called the base class or the parent class, and the Employee class is called the derived class or the child class.

The Employee class inherits from the Person class and so it automatically acquires all the public members of the Person class. It means even if the Employee class does not include FirstName, LastName properties and GetFullName() method, an object of the Employee class will have all the properties and methods of the Person class along with its own members.

Example: Inherited Members
Employee emp = new Employee();
emp.FirstName = "Steve";
emp.LastName = "Jobs";
emp.EmployeeId = 1;
emp.CompanyName = "Apple";

var fullname = emp.GetFullName(); //Steve Jobs

Note that C# does not allow a class to inherit multiple classes. A class can only achieve multiple inheritances through interfaces.

Role of Access Modifiers in Inheritance

Access modifiers play an important role in inheritance. Access modifiers of each member in the base class impact their accessibility in the derived class.

Public Members

The public members of the base class are accessible in the derived class and also become part of the derived class object.

Example: Inheritance of Public Members
class Person
{
    public string FirstName { get; set; } // can be inherited
}

class Employee : Person
{
        
}

Employee emp = new Employee();
emp.FirstName = "Bill"; // valid

Private Members

The private members of the base class cannot be accessed directly from the derived class and cannot be part of the derived class object.

Example: Inheritance of Private Members
class Person
{
    private string FirstName { get; set; }  // cannot be inherited
}

class Employee : Person
{
   
}

Employee emp = new Employee();
emp.FirstName; // Compile-time error

Protected Members

The protected members of the base class can be accessible in the derived class but cannot be a part of the derived class object.

Example: Inheritance of Protected Members
class Person
{
    protected string FirstName { get; set; }
}

class Employee : Person
{
    public int GetName()
    {
        return this.FirstName;// valid
    }
}

Employee emp = new Employee();
emp.GetName();// valid
emp.FirstName; // Compile-time error.

Internal Members

Internal members are accessible in the derived class and are part of the derived class object.

Example: Inheritance of Internal Members
class Person
{
    internal string FirstName { get; set; } 
}

class Employee : Person
{
        
}

Employee emp = new Employee();
emp.Name= "Steve";// valid

Constructors

Creating an object of the derived class will first call the constructor of the base class and then the derived class. If there are multiple levels of inheritance then the constructor of the first base class will be called and then the second base class and so on.

Example: Constructors in Inheritance
class Person
{
    public Person()
    {
	    Console.WriteLine("Person Constructor");
	}
}

class Employee : Person
{
    public Employee()
    {
	    Console.WriteLine("Employee Constructor");
	}   
}

Employee emp = new Employee();
Output:
Person Constructor
Employee Constructor

Use the base keyword in the derived class to access the public members of the base class. For example, the following calls the base class's parameterized constructor using the :base().

Example: base Keyword
class Person
{
    public Person()
    {
	    Console.WriteLine("Person Constructor");
	}

    public Person(string val)
    {
	    Console.WriteLine(val);
	}
}

class Employee : Person
{
    public Employee() : base("Parameterized constructor of base class")
    {
	    Console.WriteLine("Employee Constructor");
	}   
}

Employee emp = new Employee();
Output:
Parameterized constructor of base class
Employee Constructor

Object Initialization

You can create an instance of the derived class and assign it to a variable of the base class or derived class. The instance's properties and methods are depending on the type of variable it is assigned to. Here, a type can be a class or an interface, or an abstract class.

The following table list supported members based on a variable type and instance type.

Instance variable Instance Type Instance Members of
Base type Base type Base type
Base type Derived type Base type
Derived type Derived type Base and derived type

The following program demonstrates supported members based on the variable type:

Example: Object Creation
class Person
{
    public int Id { get; set; }  
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
}

class Employee : Person
{
    public string CompanyName { get; set; }
    public decimal Salary { get; set; }
}

public class Program
{
	public static void Main()
	{
        Person per1 = new Person();
        per1.Id = 1; //valid
        per1.FirstName = "James"; //valid
        per1.LastName = "Bond"; //valid

        //per1.CompanyName; // not supported
        //per1.Salary;  // not supported

        Person per2 = new Employee();
        per2.Id = 2; //valid
        per2.FirstName = "Bill"; //valid
        per2.LastName = "Gates"; //valid

        //per2.CompanyName; // not supported
        //per2.Salary;  // not supported
 
        Employee emp = new Employee();
        emp.Id = 1; //valid
        emp.FirstName = "Steve";//valid
        emp.LastName = "Jobs";//valid
        emp.CompanyName = "XYZ"; //valid
        emp.Salary = 10000; //valid 

        //invalid, can't assign base type to derived type
        //Employee emp = new Person();
     }
}

In the above example, the type of per2 is Person, so it will only expose public properties of the Person type even if an object type is the Employee. However, the type of emp is Employee and so it exposes all the public properties of both classes. Note that the base type object cannot be assigned to the derived type variable.


Type Conversion

The base type converts to the base class implicitly whereas the derived type must be converted to the base class explicitly using the as operator.

Example: Type Conversion From Base to Derived
public static void Display(Employee emp){
    Console.WriteLine($"Name: {emp.FirstName} {emp.LastName}");
}

public static void Main()
{
    Person per = new Employee();
    per.FirstName="Steve";
    per.LastName="Jobs";

    //Cannot convert from Person to Employee implicitly
    Display(per);//error 

    Display(per as Employee);//valid, explicit conversion

    Employee emp = new Employee();
    emp.FirstName = "Abdul";
    emp.LastName = "Kalam";
    Display(emp);//valid
}
Output:
Employee Name: Steve Jobs
Company Name:
Employee Name: Abdul Kalam
Company Name:

Types of Inheritance

There are different types of inheritance supported in C# based on how the classes are inherited.

Single Inheritance

In a single inheritance, only one derived class inherits a single base class.

Single Inheritance

Multi-level Inheritance

In multi-level inheritance, a derived class inherits from a base class and then the same derived class becomes a base class for another derived class. Practically, there are no limits on the level of inheritance, but you should avoid it.

Multi-level Inheritance

Hierarchical Inheritance

In hierarchical inheritance, multiple derived classes inherit from a single base class.

Hierarchical Inheritance

Hybrid Inheritance

Hybrid inheritance is a combination of multi-level and hierarchical inheritance.

Multiple Inheritance

Multiple Inheritance

In multiple inheritance, a class inherits from multiple interfaces. Note that C# does not support deriving multiple base classes. Use interfaces for multiple inheritance.

Hybrid Inheritance

Important Points:

  • In C#, three types can participate in inheritance: Class, Struct, and Interface.
  • A class can inherit a single class only. It cannot inherit from multiple classes.
  • A class cannot inherit from a struct.
  • A class can inherit (implement) one or more interfaces.
  • A Struct can inherit from one or more interfaces. However, it cannot inherit from another struct or class.
  • An interface can inherit from one or more interfaces but cannot inherit from a class or a struct.
  • Constructors or destructors cannot be inherited.

6. Polymorphism

Polymorphism is a Greek word that means multiple forms or shapes. You can use polymorphism if you want to have multiple forms of one or more methods of a class with the same name.

In C#, polymorphism can be achieved in two ways:

  1. Compile-time Polymorphism
  2. Run-time Polymorphism

Compile-time Polymorphism (Method Overloading)

Compile-time polymorphism is also known as method overloading. C# allows us to define more than one method with the same name but with different signatures. This is called method overloading.

Method overloading is also known as early binding or static binding because which method to call is decided at compile time, early than the runtime.

Rules for Method Overloading:

  1. Method names should be the same but method signatures must be different. Either the number of parameters, type of parameters, or order of parameters must be different.
  2. The return type of the methods does not play any role in the method overloading.
  3. Optional Parameters take precedence over implicit type conversion when deciding which method definition to bind.

The following example demonstrates the method overloading by defining multiple Print() methods with a different number of parameters of the same type.

Example: Method Overloading Method Overloading
class ConsolePrinter
{
    public void Print(string str){ 
        Console.WriteLine(str);
    }

    public void Print(string str1, string str2){ 
        Console.WriteLine($"{str1}, {str2}");
    }

    public void Print(string str1, string str2, string str3){ 
        Console.WriteLine($"{str1}, {str2}, {str3}");
    }
}

The following example demonstrates polymorphism using the same number of parameters with different types.

Example: Method Overloading
class ConsolePrinter
{
    public void Print(string str){ 
        Console.WriteLine(str);
    }

    public void Print(int a){ 
        Console.WriteLine($"Integer {a}");
    }
}

The following example demonstrates polymorphism using the same number of parameters with different sequences.

Example: Method Overloading
class ConsolePrinter
{
    public void Print(int a, string str){ 
        Console.WriteLine($"{a}, {str}");
    }

    public void Print(string str, int a){ 
        Console.WriteLine($"{a}, {str}");
    }
}

Please note that return type of methods is not considered. The following will give a compile-time error.

Example: Invalid Method Overloading
class ConsolePrinter
{
    public void Print(int a, string str)
    { 
        Console.WriteLine($"{a}, {str}");
    }

    public int Print(int a, string str){ 
        Console.WriteLine($"{a}, {str}");
        return 0;
    } 
}

The following methods overloaded with different types, sequence, and number of parameters.

Example: Method Overloading
class ConsolePrinter
{
     public void Print(string str){ 
        Console.WriteLine(str);
    }

    public void Print(string str1, string str2){ 
        Console.WriteLine($"{str1}, {str2}");
    }

    public void Print(string str1, string str2, string str3){ 
        Console.WriteLine($"{str1}, {str2}, {str3}");
    }
	
    public void Print(int a){ 
        Console.WriteLine($"Integer {a}");
    }
	
	 public void Print(int a, string str){ 
        Console.WriteLine($"{a}, {str}");
    }

    public void Print(string str, int a){ 
        Console.WriteLine($"{a}, {str}");
    }
}

Invoking Overloaded Methods

We can call the overloaded method by passing the exact parameter it requires. For example, if we want to invoke the print(string str) method that displays a string value, we will pass only one argument of string type. Likewise, if we want to invoke the Print(int a, string str) method, we will pass int and string type argument.

Example: Method Overloading
public static void Main()
{
	ConsolePrinter cp = new ConsolePrinter();
	cp.Print("Hello World!");
	cp.Print(1, "John");
}

Thus, polymorphism using method overloading plays important role in designing an application. The simplest example in .NET API is the overloads of Console.WriteLine() methods.


Runtime Polymorphism: Method Overriding

Run-time polymorphism is also known as inheritance-based polymorphism or method overriding.

Inheritance allows you to inherit a base class into a derived class and all the public members of the base class automatically become members of the derived class. However, you can redefine the base class's member in the derived class to provide a different implementation than the base class. This is called method overriding that also known as runtime polymorphism.

In C#, by default, all the members of a class are sealed and cannot be redefined in the derived class. Use the virtual keyword with a member of the base class to make it overridable, and use the override keyword in the derived class to indicate that this member of the base class is being redefined in the derived class

Example: Method Overriding
class Person
{
    public virtual void Greet()
    {
        Console.WriteLine("Hi! I am a person.");
    }
}

class Employee : Person
{
    public override void Greet()
    {
        Console.WriteLine("Hello! I am an employee.");
    }
}

As you can see, Greet() method in the Person class is defined with the virtual keyword. It means this method can be overridden in the derived class using the override keyword. In the Employee class, the Greet() method is redefined with the override keyword. Thus, the derived class extends the base class's method.

Now, the question is which method will be called, the base class's method or the derived class method? Well, it depends on the type of object. Can you guess the output of the following program?

Example: Calls to Overrided Method
Person p1 = new Person();
p1.Greet();

Person p2 = new Employee();
p2.Greet();

Employee emp = new Employee();
emp.Greet();
Output:
I am a human! I am a Manager! I am a Manager!

C# will call the method depending on the type of the object not the type of the variable. If you create an object of the Person class then it will call the Greet() method of the Person class and if you create an object of the Employee class then it will call the Greet() method of the Employee class no matter to which type of variable it assigned to.

As you learned in the previous chapter the C# compiler decides which methods to call at the compile time in the compile-time polymorphism. In the run time polymorphism, it will be decided at run time depending upon the type of an object.

To understand why method overriding is called the runtime polymorphism, look at the following example.

Example: Runtime Polymorphism
class Program
{
    public static void Display(Person p){ 
        p.Greet();
    }

    public static void Main()
    {
        Person p1 = new Person();
        Display(p1);
            
        Person p2 = new Employee();
        Display(p2);
            
        Employee emp = new Employee();
        Display(emp);
    }
}
Output:
I am a human! I am the Manager! I am the Manager!

In the above example, the Display() method takes a parameter of the Person type. Thus, you can pass an object of the Person type or the Employee type when you call the Display() method. The Display() method does not know the type of parameter you passed at compile time. It can be anything at runtime. That's why method overriding is called run-time polymorphism.


Rules for Overriding:

  • A method, property, indexer, or event can be overridden in the derived class.
  • Static methods cannot be overridden.
  • Must use virtual keyword in the base class methods to indicate that the methods can be overridden.
  • Must use the override keyword in the derived class to override the base class method.

Learn more about virual and override keywords.

What will happen if we don't use virtual and override keywords and redefine the base class method in the derived class? Learn about it below.


7. Method Hiding in C#

In the previous point, you learned about method overriding using the virtual and override keywords. Here you will learn what will happen if we don't use these keywords and still redefine the base class's methods in the derived class?

Look at the following example.

Example: Override Method without Virtual and Override Keywords
class Person
{
    public virtual void Greet()
    {
        Console.WriteLine("Hi! I am a person.");
    }
}

class Employee : Person
{
    public override void Greet()
    {
        Console.WriteLine("Hello! I am an employee.");
    }
}

As you can see, the Greet() method is defined in the base class as well as the derived class without the virtual and override keywords. Now, guess the output of the following program.

Example: Method Hiding
class Program
{
    public static void Main()
    {
        Person p1 = new Person();
        p1.Greet(); 
            
        Person p2 = new Employee();
        P2.Greet();

        Employee emp = new Employee();
        emp.Greet();
    }
}

When you compile the above program, it will give the following warning.

warning CS0108: 'Employee.Greet()' hides inherited member 'Person.Greet()'. 
Use the new keyword if hiding was intended.

However, the above program will successfully run and will give the following output.

Output:
Hi! I am a person. Hello! I am an employee. Hello! I am an employee.

As you can see, the calling of the Greet() method does not depend on the type of object. Here, Employee.Greet() hides the base class method Person.Greet(). This is called method hiding (of base class) in C#.


Method Hiding using new Keyword

Use the new keyword in the derived class to hide the base class method. This will be useful when you are resuing third-party APIs where you don't have control over the base class.

The new keyword will not give the above warning. The following will give the same result but will not give any warning at compile time.

Example: new Keyword
class Person
{
    public void Greet()
    {
        Console.WriteLine("I am a person!");
    }
}

class Employee : Person
{
    public new void Greet()
    {
        Console.WriteLine("I am the Manager!");
    }
}

By using the new keyword with the derived class method, C# treats it as a new method of the derived class and hides the base class method. The Employee.Greet() will be treated as a new method.

The new keyword preserves the relationships that produce that output, but it suppresses the warning. The variables of type base class continue to access the members of the base class, and the variable of type derived class continues to access members in the derived class first, and then consider members inherited from the base class.

Learn when to use override and new keywords.

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post