One of my favorite interview questions is what the difference is between a virtual and a non-virtual method. If you were learning development in the early 90s, you heard about a relatively spirited controversy regarding the use of virtual functions. The proponents shared the programming flexibility that it afforded. The detractors warned of the performance implications. Luckily, Moore’s law has silenced the detractors. The impact of the extra lookups that must be done for virtual functions has long been decided as trivial.
Relatively few folks are aware what the impacts of making methods virtual are, and of those who fully understand virtual methods only a few really understand how they impact the use of interfaces in .NET. This article is designed to walk through the results you can expect when utilizing both non-virtual and virtual methods via an interface in .NET.
Before explaining virtual methods, it’s important to review one of the fundamental concepts to object-oriented programming. Any class that is derived from a base class can be used anywhere the base class is expected. This means that if I have a class—SubB—which derives (or is based on) BaseA and I have methods that expect an instance of BaseA, I can pass in an instance of SubB instead.
This is handy when you need to operate on an object but don’t necessarily know its specific type. For instance, one might create a general payment object that is used for the base of specific payment types. So, there may be classes for PayPal, Credit Card, ACH (electronic checks), and so forth. For each of those, there may be a method to authorize. This authorize method gets defined in the base payment class and then a method can take in a generic payment base object and call an authorize method—without knowing whether the payment is really a credit card, PayPal payment, and so on.
The interfaces concept extends this concept by eliminating the need to directly derive from a base payments class. Consider a case where you want to do a new payment class that is so fundamentally different that you don’t want to use the basic functionality provided in the payment base class, but you need to still have a way to have your specific payment object used by the same mechanisms as the rest of the payment objects. This might happen if you’re encapsulating a third-party credit card processing class. You must base your object on their class—but you still need to support your payment interface. In this case, you define an interface—a set of methods, properties, and fields that you guarantee are a part of the class and you publish them as an interface.
Because you’ve published an interface on the object, the object can be referred to by the interface just as it may have been referred to by one of its base classes. The net effect is you get the ability to ignore the specifics of the object in question without having to maintain a single inheritance hierarchy—something that can be difficult to do.
The argument raging in the late 80s and early 90s surrounded the use of virtual methods. When a compiler encounters a typical method, it jumps to a specific location in memory and runs whatever is there. The idea of a virtual method is that calling the method doesn’t directly jump to the method to be run. Instead, it looks in a specific spot for the method to call. That spot is updated by subclasses so that their methods are at the target address—instead of the base class’ method.
The net effect is that even though you can pass an object around as though its base object it will still behave (call methods from) the sub-class. For instance, say that you have a base class vehicle with a virtual method called Go. The default implementation is to start rolling forward. However, there’s a subclass rocket that the correct action for a method called Go would be to start moving upward. By defining Go as a virtual method, the correct Rocket version of Go would be called anytime a Rocket object is used—even if the Rocket object is being held in a Vehicle reference.
If the method were not virtualized, whenever you referred to an object with a Vehicle it would call the Vehicle.Go method—not the Rocket.Go method if the object were really an instance of the Rocket class. In other words, it would be possible to call the wrong method on an object.
Virtual methods are powerful and although they have a slight performance impact, they can be essential for making objects behave the way they should.
Interfaces and Virtual
So, to gain from virtual methods is insurance that the method being called is always the correct version of the method. Interfaces allow you to remove the concern about the object hierarchy. It sounds like the two are good partners in making software easier to build and easier to understand. They can be. However, it’s possible to implement an interface without virtualizing the property or method. In other words, you can implement the interface with regular methods.
The impact of this is that the inheritance rules for what method you get when you call properties and methods in the interface get relatively complex. The following table demonstrates what happens when you call a method from the object, from the base object, and from the interface for three different implementation approaches:
- No virtual methods and a “new” override of the method: In other words, the base class and the derived class both have a method with the same name.
- No virtual methods and an explicit implementation of the interface method: The method for the interface is explicitly defined. So, the method name in the derived class is prefixed with the interface name.
- Virtual methods: The methods of the interface are defined as virtual methods.
|Non-Virtual, Sub overrides method
|Non-Virtual, Sub explicitly defines interface
The first line shows some expected behavior; when calling a method on an object from the base class on a non-virtualized method the base method, not the method in the derived class is used. However, continuing over the interface use the method from the base class too. This is bad if you need the interface to reflect the actual object.
So, if you try to get around this by explicitly implementing the interface, you create a situation where the only place where the method is overridden is for the interface. You get the correct method when referring to the interface but not even when you refer to the object itself. That’s because the name is specifically associated to the interface.
If you define the method as virtual in the base class, you get the appropriate method from the derived class no matter how you call it. This is the right behavior for most situations. So, the question becomes, why not use virtual methods with all implementations of interfaces?
The answer lies in the fact that, from the Interface declaration, you cannot require that the class implementing the interface use virtual methods and properties. Although the class can implement the interface via virtual methods and virtual properties, it doesn’t have to.
What to Do
So, it’s up to you to verify that the methods that you have to implement an interface are implemented as virtual methods and properties. You have to think about this when you implement interfaces for classes that may be derived from. It’s one of those nasty little details that you still have to pay attention to when creating classes.
Download the Code
You can download the code that accompanies this article here.
Robert Bogue, MCSE (NT4/W2K), MCSA:Security, A+, Network+, Server+, I-Net+, IT Project+, E-Biz+, CDIA+ has contributed to more than 100 book projects and numerous other publishing projects. He was recently honored to become a Microsoft MVP for Microsoft Commerce Server and before that Microsoft Windows Servers-Networking. Robert blogs at http://www.thorprojects.com/blog. You can reach Robert at Rob.Bogue@thorprojects.com.