.NET Interface-based Programming (Cont.) Defining and Using Interfaces To implement an interface, all a class has to do is derive from that interface. Listing 1 shows the class MyClass implementing the interface IMyInterface. | " | By disallowing any kind of implementation detail in interfaces (such as method implementation, constants, static members, and constructors), .NET promotes loose coupling between the service providers. The client interface acts like a binary shield, isolating both parties from each other.
| " |
As trivial as Listing 1 is, it does demonstrate a number of important points. First, interfaces have visibility?an interface can be private to its assembly (using the internal visibility modifier) or it can be used from outside the assembly (the public visibility modifier), as in the listing. Second, even though the methods the interface defines have no visibility modifiers, they are by definition public and the implementing class has to declare its interface methods as public. Third, there is no need for using new or override to qualify the method redefinition in the subclass because an interface method by nature cannot have any implementation and, therefore, there is nothing to override. Fourth, the class must implement all the methods the interface defines, without exception. If the class is an abstract class, then it can redefine the methods without providing concrete implementation. To interact with the object using the interface, all a client has to do is down cast the object to the interface, similar to using any other base type. Using the same definitions as in Listing 1, the client code might be: IMyInterface obj; obj = new MyClass(); obj.Method1();
Interfaces promote loose coupling between clients and objects. When using interfaces, there is a level of indirection between the client code and the object implementing the interface. If the client was not responsible for instantiating the object, then there is nothing in the client code pertaining to the object hidden behind the interface shield. Because of this, there could be many possible implementations of the same interface, such as: public interface IMyInterface {...} public class MyClass : IMyInterface {...} public class MyOtherClass : IMyInterface {...}
When a client obtains an interface reference by creating an object of type MyClass, the client is actually saying to .NET, "give me the interpretation of MyClass to the way IMyInterface should be implemented." Interfaces and Error Handling This sort of casting down from a class instance to an interface: IMyInterface obj; obj = new MyClass(); obj.Method1();
is called implicit cast because the compiler is required to figure out which type to down cast the class to. When using implicit cast, the compiler enforces type safety. If the class MyClass does not implement the IMyInterface interface, then the compiler refuses to generate the code and produces a compilation error. The compiler is able to do that because it can read the class's metadata and can tell in advance that the class does not derive from the interface. However, there are a number of cases where developers use an explicit cast instead of the implicit cast. The first is for readability. Sometimes you want to convey to readers explicitly what interface to expect to get back: obj.Method1(); IMyInterface obj; /* Some code here */ obj = (IMyInterface) new MyClass(); obj.Method1();
Explicit down cast to the interface is at the expense of type safety. Even if the class does not support the interface, the compiler still compiles the client's code and .NET throws an exception at runtime. The second case where explicit cast is used is with class factories. In object-oriented programming, clients often do not create objects directly, but rather get their instances from some class factory (see the Abstract Factory design pattern in Object Oriented Design Patterns, pp 87, ADW 1994). In that case, explicit down cast is unavoidable because the class factory returns some generic base type, which is usually object: IClassFactory factory; /* Some code to initialize the class factory */ IMyInterface obj; //GetObject() returns System.Object obj = (IMyInterface)factory.GetObject(); obj.Method1();
The third case that mandates explicit down cast is when you have one interface the class implements, and you want to use it to get hold of another interface the class supports. Consider the code in Listing 2. Even when the client uses implicit cast to get hold of the first interface, it must do an explicit down cast to obtain the second. In all of these examples of explicit down cast, you must incorporate error handling in case the type you are trying to down cast from does not support the interface. You can use try and catch statements to handle the exception, but you can also use the as operator to do a safe down cast. The as operator performs the cast if it is legal and assigns a value to the variable. However, if a down cast is not possible, the as operator assigns null to the interface variable, instead of throwing an exception. Listing 3 shows how to use the as operator to perform a safe cast that does not result in an exception in case of an error. Interestingly, using the as operator to determine whether a particular object supports a given interface is semantically identical to COM's QueryInterface() method. Both mechanisms allow clients to defensively obtain an interface from an object and handle the situation when it does not support it. | & | | Interface Naming
When you name a new interface type, prefix it with a capital "I," followed by a capital letter of the domain term, such as IAccount, ICalculator, etc. Use the "I" prefix even if the domain term itself starts with an "I" (such as IInternetManager). In spite of the fact that .NET tries to do away with the old Windows and C++ Hungarian naming notations (prefixing a variable name with its type), the "I" prefix is a direct legacy from COM, and that tradition is maintained in .NET. |