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
- 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}
- 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}
- 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
- Abstract (C# Reference): https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract
- Virtual (C# Reference): /en-us/dotnet/csharp/language-reference/keywords/virtual