Practical .NET
Make Objects Look Alike... and Tell Them Apart
Here's everything you need to know to simplify your code about how to make classes look alike and then, when you need to, tell them apart.
When designing classes you shouldn't hesitate to design specific objects for specific tasks. This can complicate code that has to deal with multiple types of objects. To simplify that code you have to make different objects look alike... and then be able to tell them apart. You've got a bunch of options in both languages to do that -- and one more in C#.
For instance, let's say you have three types of classes -- Customer, PremiumCustomer, and DeadbeatCustomer -- but you have a routine that checks the CreditRating on any of them. You could declare the parameter that the method accepts as type Object, for instance, and have it work with anything:
Public Function CheckCreditRating (cust as Object) As Boolean
End Function
In C#:
public bool CheckCreditRating (Object cust)
{
}
You can do this because everything in .NET inherits from Object -- you can declare a variable as any type in a class' inheritance chain and the variable will work the object. If you have a PremiumCustomer and a DeadBeatCustomer that both inherit from Customer, then you can declare your variable as Customer:
Public Function CheckCreditRating (cust as Customer) As Boolean
End Function
public bool CheckCreditRating (Customer cust)
{
}
Effectively then, inheritance makes different objects look alike: All the .NET objects can be accessed with a variable declared as Object because everything inherits from Object. PremiumCustomer and DeadBeatCustomer objects can be accessed with a variable declared as Customer if both classes inherit from Customer.
Interfaces provide another, more flexible way for different objects to look alike. While a class can only inherit from one other class, it can implement as many interfaces as you want. Here's the Visual Basic way of declaring an interface:
Public Interface ICustomer
End Interface
Here's the C# way:
public interface ICustomer
{
}
Now you can implement the interface in any class you want:
Public Class Customer
Implements ICustomer
End Class
public class Customer: ICustomer
{
}
You don't have to have your interface names begin with the letter I, but it's a .NET tradition to do so.
If Customer, PremiumCustomer and DeadBeatCustomer all implement the ICustomer interface, then you can declare a variable as ICustomer and use it with all three classes. These methods can accept any class that implements the ICustomer interface:
Public Function CheckCreditRating (cust as ICustomer) As Boolean
End Function
public bool CheckCreditRating (ICustomer cust)
{
}
Of course, nothing is free. If you make objects look alike through inheritance, you also pick up the code built into any class you inherit from. Using interfaces obliges you to write all the code for the interface's methods and properties. If you aren't sharing functionality, interfaces are your best choice -- especially if you want classes that would otherwise have nothing to do with each other to look alike. If I wanted to be able to treat Customer and Vendor objects alike when it comes to checking credit ratings, I wouldn't try to have Customers inherit from Vendors or both of them inherit from some common object: I'd have them all implement an ICreditRating interface.
Telling Them Apart
The trick is that you can only access the methods and properties that are available through the interface you've declared your variable with. For instance, if you declare your variable as Object, then your variable will only give you access to the members of the Object class' interface (GetType, ToString, etc.). If you declare a variable as Customer, then you'll only be able to access the members defined in the Customer class. If you define a method in the PremiumCustomer class that isn't part of the base Customer class, you won't be able to access that method with a variable defined as Customer.
So you'll need to do a cast. With objects, when you cast a variable what you're really doing is picking which interface on the object you'll be accessing. This Visual Basic code allows you to access the PremiumCustomer interface on the object passed into the method:
Public Function CheckCreditRating (cust as Customer) As Boolean
Dim pm As PremiumCustomer
pm = CType(cust, PremiumCustomer)
End Function
Even as a long time Visual Basic coder, I have to admit that I think the C# syntax for casting is more expressive than the Visual Basic syntax:
public bool CheckCreditRating (Customer cust)
{
PremiumCustomer pm;
pm = (PremiumCustomer) cust;
}
Of course, if the object passed in doesn't have the PremiumCustomer interface (in this object model that would mean that the object passed in wasn't actually a PremiumCustomer) this code will blow up. So sensible programmers will check to make sure the cast will work before trying it. In Visual Basic you use the TypeOf keyword like this:
If TypeOf cust Is PremiumCustomer Then
pm = CType(cust, Customer)
End If
In C#, the code uses the is keyword like this:
if (cust is PremiumCustomer)
{
pm = (PremiumCustomer) cust;
}
That's a lot of boring code to write (and have to do it again and again). Both languages provide a way to check if the interface is available and extract it (if the interface isn't available, the result is Nothing or null). In Visual Basic you use the DirectCast function to set your variable and check to see if it was set to Nothing, like this:
pm = DirectCast(cust, PremiumCustomer)
If pm IsNot Nothing Then
... do something with pm as a PremimumCustomer
In C#, you use the as keyword, like this:
pm = cust as PremiumCustomer;
if (pm != null)
{
... do something with pm as a PremimumCustomer
But, in C# 4, you have another option: You can declare your variable as dynamic and let the compiler figure out if the methods are available at runtime:
public void CheckCreditRating(dynamic cust)
{
if (cust is PremiumCustomer)
{
... use cust as a PremiumCustomer
You'll give up IntelliSense support at design time (and a couple of milliseconds of performance at runtime), but the code is a little bit cleaner.
There you go: Everything you need to know about making classes look alike and then tell them apart.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.