Share via


C#: Difference (and Similarity) between Virtual and Abstract (Method/Property) With Example


Introduction

Previously in the article, "C#: Usage of Interfaces and Abstract Classes in Real World (and Difference between them)", the differences between Interface and Abstract classes are discussed. Another common topic of discussion is the difference and similarity between Virtual and Abstract keywords in C# (.NET). This article focuses to bust the reality of these two keywords and how they are used in real world scenario (I mean programming). Let's jump into it right away.

Establish: Difference Between Virtual and Abstract Keywords

The main and most important difference between Virtual and Abstract Keywords is that Virtual method/property may or may not be overriden in the derived class. Whereas, in case of abstract keyword, you have to override the method or property, or else the compiler will throw error.

Establish: Similarity Between Virtual and Abstract Keywords

There are many similarities. The most obvious being, if a method or property has any of these keywords then they can be overriden in the derived class or else a property or a method can never be overriden. Another similarity, which is not that common in knowledge but is obvious due to their implantation is that both keyword, if specified for a method or property, the access modifier has to be either public, protected, internal or protected internal. Since, private access modifiers do not let any other class (even derived classes) to view any property or method with such access modifier and hence, these two keywords, virtual and abstract, can not have the private modifiers. Although you can use the internal and protected internal access modifiers but there are exceptions, which is discussed in the "Some Common Pitfalls" section. So keep tuned in.

Business Logic For the Console App

To explore the similarities and difference between virtual and abstract, let's take the same real world example, which we used in "C#: Usage of Interfaces and Abstract Classes in Real World (and Difference between them)". In case if you have missed that article, I would highly recommend to read it. Anyway, let's have a quick revision of the example we used in that aforementioned article. We have to developing a banking system where we will be implementing an abstract class of Customers, which needs to be extended to several other derived classes of Customers for different regions (branches) of the bank. Now, since we know what we are trying to implement, let's do it.

The Development of Console App

Implementation

First, let's write the Abstract Class with both virtual method and abstract method. I will give you the explanation of why we used abstract keyword for some method and virtual for the other after the implementation. The code is as follows:

using System;
namespace Virtual_to_Abstract
{
    // Abstract class for all customers
    public abstract  class CustomerAbstractClass
    {
        // Declare the GetCustomerType() default method here
        public abstract  int GetCustomerType();
 
        // Implement the GetCustomerTypeName() default method here
        public abstract  string GetCustomerTypeName();
 
        // Implement the GetCountryRegion() default method here
        public virtual  string GetCountryRegion()
        {
            return "UK/EU";
        }
    }
}

The method GetCustomerType() holds a unique id for the type of Customer genre a particular Customer belong to. Since, the returned result of this method will vary on each derived class (because no two types of Customer derived class will have same id), we have chosen to use the abstract keyword for this method and the same logic goes for the GetCustomerTypeName() method, which signifies the name of the derived Customer class. We used virtual keyword for GetCountryRegion() method because the region of different branches of the bank might remain same but may differ for some varied derived Customer class. 

Now, let's write a new derived class named, UKEUCustomer, which serves as the class for all UK and EU customer of that bank. The code is as follows:

using System;
namespace Virtual_to_Abstract
{
    public class  UKEUCustomer: CustomerAbstractClass
    {
        private string  CustomerName;
 
        public UKEUCustomer(string CustomerName)
        {
            this.CustomerName = CustomerName;
        }
 
        // Get Customer Name
        public string  GetName()
        {
            return CustomerName;
        }
 
        // Using override to return the already
        //implemented method, GetCustomerType() of Base class, which is CustomerAbstractClass
        public override  int GetCustomerType()
        {
            return 1;
        }
 
        // Using override to return the already
        // implemented method, GetCustomerTypeName() of Base class, which is CustomerAbstractClass
        public override  string GetCustomerTypeName()
        {
            return "UK/EU Citizen";
        }
 
        /*
        // If not required implementing
        public override int GetCustomerType()
        {
            throw new NotImplementedException();
        }
 
        // If not required implementing
        public override string GetCustomerTypeName()
        {
            throw new NotImplementedException();
        }
        */
    }
}

In this class, since the region remains the same, which is "UK/EU", so we did not override the method, GetCountryRegion(), for which we have used the virtual keyword. But we had to override the methods, GetCustomerType() and GetCustomerTypeName(), since the implementation of these two methods will always vary with each derived class.

Now. let's implement another derived class named, NonUKEUCustomer, which signifies a Customer class outside the UK/EU region. The code is as follows:

using System;
namespace Virtual_to_Abstract
{
    public class  NonUKEUCustomer: CustomerAbstractClass
    {
        private string  CustomerName;
 
        public NonUKEUCustomer(string CustomerName)
        {
            this.CustomerName = CustomerName;
        }
 
        // Get Customer Name
        public string  GetName()
        {
            return CustomerName;
        }
 
        // Using override to return the already
        //implemented method, GetCustomerType() of Base class, which is CustomerAbstractClass
        public override  int GetCustomerType()
        {
            return 2;
        }
 
        // Using override to return the already
        //implemented method, GetCustomerTypeName() of Base class, which is CustomerAbstractClass
        public override  string GetCustomerTypeName()
        {
            return "Non UK/EU Citizen";
        }
 
        // Implement the GetCountryRegion() default method here
        public new  string GetCountryRegion()
        {
            return "Non UK/EU - Rest of the world";
        }
    }
}

Notice, in this one, we implement the method GetCountryRegion() method but we did not override the original implementation from the base class. We could have easily overriden the method of the base class easily but the reason to use the new keyword is to show that even if we do not override a virtual method of the base class but we can still implement as new method/property in the derived class with the same name without any issue.

Now, let's implement another derived class named, BritishIslandCustomer, which implements the Customer derived class for all customer from British Islands, hence UK citizens but the region is not in UK/EU. The code is as follows:

using System;
namespace Virtual_to_Abstract
{
    public class  BritishIslandCustomer: CustomerAbstractClass
    {
        private string  CustomerName;
 
        public BritishIslandCustomer(string CustomerName)
        {
            this.CustomerName = CustomerName;
        }
 
        // Get Customer Name
        public string  GetName()
        {
            return CustomerName;
        }
 
        // Using override to return the already
        //implemented method, GetCustomerType() of Base class, which is CustomerAbstractClass
        public override  int GetCustomerType()
        {
            return 3;
        }
 
        // Using override to return the already
        //implemented method, GetCustomerTypeName() of Base class, which is CustomerAbstractClass
        public override  string GetCustomerTypeName()
        {
            return "UK - British Island Citizen";
        }
 
        // Implement the GetCountryRegion() default method here
        public override  string GetCountryRegion()
        {
            return "Non UK/EU - British Island";
        }
    }
}

Notice, that in this derived class, we ave finally overriden all the method from the abstract base class to return the desired results in this derived class.

Now if we, run the following program class to check the desired output then we can see that our implementation of each derived classes has been successful as per the business requirement of the program. 
The program class consists of the following code:

using System;
 
namespace Virtual_to_Abstract
{
    class Program
    {
        static void  Main(string[] args)
        {
            // For UK citizen
            UKEUCustomer ukCustomer = new  UKEUCustomer("Jon Skeet");
 
            Console.WriteLine("Customer Name: " + ukCustomer.GetName());
            Console.WriteLine("Customer Type Id: " + ukCustomer.GetCustomerType());
            Console.WriteLine("Customer Type Name: " + ukCustomer.GetCustomerTypeName());
            Console.WriteLine("Customer Region: " + ukCustomer.GetCountryRegion());
            Console.WriteLine("\n");
 
            // For non UK citizen
            NonUKEUCustomer nonUkCustomer = new  NonUKEUCustomer("Somdip Dey");
 
            Console.WriteLine("Customer Name: " + nonUkCustomer.GetName());
            Console.WriteLine("Customer Type Id: " + nonUkCustomer.GetCustomerType());
            Console.WriteLine("Customer Type Name: " + nonUkCustomer.GetCustomerTypeName());
            Console.WriteLine("Customer Region: " + nonUkCustomer.GetCountryRegion());
            Console.WriteLine("\n");
 
            // For UK citizen in British Island
            BritishIslandCustomer britishIslandCUstomer = new  BritishIslandCustomer("Jon Doe");
 
            Console.WriteLine("Customer Name: " + britishIslandCUstomer.GetName());
            Console.WriteLine("Customer Type Id: " + britishIslandCUstomer.GetCustomerType());
            Console.WriteLine("Customer Type Name: " + britishIslandCUstomer.GetCustomerTypeName());
            Console.WriteLine("Customer Region: " + britishIslandCUstomer.GetCountryRegion());
            Console.WriteLine("\n");
        }
    }
}

The Result/Output

If we build and run the before mentioned program then we can get the following result:

Some Common Pitfalls

  1. You have to override the method or property, which has abstract keyword
    If you do not override the method or property then the compiler will throw an error, something similar to this:
{DerivedClass} does not implement inherited abstract member {AbstractClass}.{AsbtractMethod}
  1. You have to use either public, protected, internal or protected internal access modifiers
    If you use private then the compiler will again throw an error. and in case if you use internal or protected 
    internal make sure that the derived classes where the methods are overriden, the overriden method also has to have the same internal or protected internal access modifier like the methods/properties in the base class. If you don't then you will receive an error, something similar to this:
{DerviedClass}.{OverridenMethod}: Can not change access modifiers when overriding 'internal'/'protected internal' inherited member {BaseClass}.{ToBeOverridenClass}
  1. If you have not implemented a virtual member (method/property) of the base class by overriding and have implemented the method using new keyword then the error mentioned in [2] is not faced.

References