This tutorial—Interface-Based Programming—is from Programming .NET Components, by Juval Lowy. Copyright © 2005 O'Reilly Media, Inc. All rights reserved. Read the book review!
While going through the web, I came to know about this article. It found to be best article I ever seen which explains about Interface in very easy language. I appreciate the author for explaining most complex concept in .Net with very easy language. This article compelled me to purchase this book.
Interface-Based Programming
Separation of interface from implementation is a core principle of component-oriented programming. When you separate interface from implementation, the client is coded against an abstraction of a service (the interface), not a particular implementation of it (the object). As a result, changing an implementation detail on the server side (or even switching to a different service provider altogether) doesn’t affect the client. This chapter starts by presenting .NET interfaces and describing what options are available to .NET developers when it comes to enforcing the separation of interface from implementation. It then addresses a set of practical issues involving the definition and use of interfaces, such as how to implement multiple interfaces and how to combine interfaces and class hierarchies. After a detailed look at generic interfaces, the chapter ends with a discussion of interface design and factoring guidelines.Separating Interface from Implementation
In both C# and Visual Basic 2005, the reserved wordinterface
defines a CLR reference
type that can’t have any implementation, can’t be instantiated, and has only
public members. Saying that an interface can’t have implementation means that it’s
as if all the interface’s methods and properties were abstract. Saying it can’t be
instantiated means the same as if the interface were an abstract class (or MustInherit
in Visual Basic 2005). For example, this interface definition:
public interface IMyInterface { void Method1( ); void Method2( ); void Method3( ); }is almost equivalent to this class definition:
public abstract class MyInterface { public abstract void Method1( ); public abstract void Method2( ); public abstract void Method3( ); }In traditional object-oriented programming, you typically use an abstract class to define a service abstraction. The abstract class serves to define a set of signatures that multiple classes will implement after deriving from the abstract class. When different service providers share a common base class, they all become polymorphic with that service abstraction, and the client can potentially switch between providers with minimum changes. There are a few important differences between an abstract class and an interface:
- An abstract class can still have implementation: it can have member variables or non-abstract methods or properties. An interface can’t have implementation or member variables.
- A .NET class can derive from only one base class, even if that base class is abstract. However, a .NET class can implement as many interfaces as required.
- An abstract class can derive from any other class or from one or more interfaces. An interface can derive only from other interfaces.
- An abstract class can have nonpublic (protected or private) methods and properties, even if they are all abstract. In an interface, by definition, all members are public.
- An abstract class can have static methods and static members and can define constants. An interface can have none of those.
- An abstract class can have constructors. An interface can’t.
Because interfaces can’t be instantiated, .NET forces clients to choose a particular implementation to instantiate. Having only public members in an interface complements the contract semantics nicely: you would not want a contract with hidden clauses or "fine print." Everything the contract implies should be public and well defined. The more explicit and well defined a contract is, the less likely it is that there will be conflicts down the road regarding exactly what the class providing the service is required to do. The class implementing the interface must implement all the interface members without exception, because it has committed to providing this exact service definition. An interface can extend only other interfaces, not classes. By deriving a new interface from an existing interface, you define a new and specialized contract, and any class that implements that interface must implement all members of the base interface(s). A class can choose to implement multiple interfaces, just as a person can choose to commit to multiple contracts.
Interface Implementation
To implement an interface, all a class has to do is derive from it. Example 3-1 shows the classMyClass
implementing the interface IMyInterface
.
Example 3-1. Defining and implementing an interface
public interface IMyInterface { void Method1( ); void Method2( ); void Method3( ); } public class MyClass : IMyInterface { public void Method1( ) {...} public void Method2( ) {...} public void Method3( ) {...} //other class members }As trivial as Example 3-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
access modifier) or it can be used from outside the assembly (with the
public
access modifier), as in Example 3-1. Second, even though the methods the
interface defines have no access modifiers, they are by definition public, and the
implementing class has to declare its interface methods as public. Third, there is no
need to use new
or override
to qualify the
method redefinition in the subclass,
because an interface method by its very nature can’t have any
implementation and therefore has nothing to override. (If you aren’t
familiar with the new
or override
keywords,
see the sidebar "C# Inheritance Directives" later in this chapter.) Finally, the
class must implement all the methods the interface defines, without exception. If the
class is an abstract class, it can redefine the methods without providing concrete
implementation.
Example 3-2 shows how to define and implement an interface in Visual Basic 2005. In Visual Basic 2005, you need to state which interface method a class method corresponds to. As long as the signature (i.e., the parameters and return value) matches, you can even use a different name for the method. In addition, because the default accessibility in Visual Basic 2005 is public, unlike in C#, adding the
Public
qualifier is optional.
Example 3-2. Defining and implementing an interface in Visual Basic 2005
Public Interface IMyInterface Sub Method1( ) Sub Method2( ) Sub Method3( ) End Interface Public Class SomeClass Implements IMyInterface Public Sub Method1( ) Implements IMyInterface.Method1 ... End Sub Public Sub Method2( ) Implements IMyInterface.Method2 ... End Sub Public Sub Method3( ) Implements IMyInterface.Method3 ... End Sub End ClassTo interact with an object using an interface, all a client has to do is instantiate a concrete class that supports the interface and assign that object to an interface variable, similar to using any other base type. Using the same definitions as in Example 3-1, the client code might be:
IMyInterface obj; obj = new MyClass( ); obj.Method1( );
Interfaces promote loose coupling between clients and objects. When you use interfaces, there’s a level of indirection between the client’s code and the object implementing the interface. If the client wasn’t responsible for instantiating the object, there is nothing in the client code that pertains to the object hidden behind the interface shield. There can 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 MyClass’s interpretation of the way
IMyInterface
should be implemented."
Treating interfaces as binary contracts, which shields clients from changes made to the service providers, is exactly the idea behind COM interfaces, and logically, .NET interfaces have the same semantics as COM interfaces. If you are an experienced COM developer or architect, working with interfaces is probably second nature to you, and you will feel right at home with .NET interfaces.
However, unlike COM, .NET doesn’t enforce separation of the interface from the implementation. For example, using the definitions in Example 3-1, the client’s code can also be:
MyClass obj; obj = new MyClass( ); obj.Method1( );Because of the way the server in Example 3-1 implements the interface (as public members), nothing prevents the client from programming directly against the object providing the service, instead of the interface. I believe this is because .NET tries to make component-oriented programming accessible to all developers, including those who have trouble with the more abstract concepts of interface-based programming (see the section “.NET Adherence to Component Principles” in Chapter 1). The fact that something is possible, of course, doesn’t mean you should go ahead and do it. Disciplined .NET developers should always enforce the separation, to retain the benefits of interface-based programming.
Explicit Interface Implementation
The way of implementing an interface shown in the previous section is called implicit interface implementation, because a public method with a name and signature that match those of an interface method is implicitly assumed to be an implementation of that interface method.Example 3-3 demonstrates a simple technique that allows server developers to enforce the separation of the interface from the implementation. The server implementing the interface can actually prevent clients from accessing the interface methods directly by using explicit interface implementation. Implementing an interface explicitly means qualifying each interface member name with the name of the interface that defines it.
Example 3-3. Explicitly implementing an interface
public interface IMyInterface { void Method1( ); void Method2( ); } public class MyClass : IMyInterface { void IMyInterface.Method1( ) {...} void IMyInterface.Method2( ) {...} //Other methods and members }Note that the interface members must be implicitly defined as private at the class’s scope; you can’t use any explicit access modifiers on them, including
private
. The
only way clients can invoke the methods of explicitly implemented interfaces is by
accessing them via the interface:
IMyInterface obj; obj = new MyClass( ); obj.Method1( );To explicitly implement an interface in Visual Basic 2005, you need to explicitly set the method access to
Private
, as in Example 3-4.
Example 3-4. Explicitly implementing an interface in Visual Basic 2005
Public Interface IMyInterface Sub Method1( ) Sub Method2( ) End Interface Public Class SomeClass Implements IMyInterface Private Sub Method1( ) Implements IMyInterface.Method1 ... End Sub Private Sub Method2( ) Implements IMyInterface.Method2 ... End Sub End ClassYou should avoid mixing and matching explicit and implicit interface implementations, as in the following fragment:
//Avoid mixing and matching: public interface IMyInterface { void Method1( ); void Method2( ); } public class MyClass : IMyInterface { void IMyInterface.Method1( ) {...} public void Method2( ) {...} //Other methods and members }Although .NET lets you mix and match implementation methods, for consistency, you should avoid it. Such mix and match forces the client to adjust its references depending on whether a particular method is accessible via an interface or directly via the object.
Assemblies with Interfaces Only |
Because interfaces can be implemented by multiple components, it’s good practice to put them in a separate assembly from that of the implementing components. Maintaining a separate assembly that contains only interfaces allows concurrent development of the server and the client, once the two parties have agreed on the interfaces. Such assemblies also extend the separation of interface from implementation to the codepackaging units. |
Working with Interfaces
Now that you have learned the importance of using interfaces in your componentbased application, it’s time to examine a number of practical issues regarding working with interfaces and tying them to the rest of your application. Later in this chapter, you will also see the support Visual Studio 2005 offers component developers when it comes to adding implementation to your classes for predefined interfaces.Interfaces and Type Safety
Interfaces are abstract types and, as such, can’t be used directly. To use an interface, you need to cast into an interface reference an object that supports it. There are two types of casting—implicit and explicit—and which type you use has an impact on type safety.Assigning a class instance to an interface variable directly is called an implicit cast, because the compiler is required to figure out which type to cast the class to:
IMyInterface obj; obj = new MyClass( ); obj.Method1( );When you use implicit casts, the compiler enforces type safety. If the class
MyClass
doesn’t implement the IMyInterface
interface, the compiler refuses to generate the
code and produces a compilation error. The compiler can do that because it can read
the class’s metadata and can tell in advance that the class doesn’t derive from the
interface. However, there are a number of cases where you cannot use implicit casting.
In such cases, you can use explicit cast instead. Explicit casting means casting
one type to another type:
IMyInterface obj; /* Some code here */ obj = (IMyInterface)new MyClass( ); obj.Method1( );However, bear in mind that explicit casts to an interface are made at the expense of type safety. Even if the class doesn’t support the interface, the compiler will compile the client’s code, and .NET will throw an exception at runtime when the cast fails.
An example where implicit cast is unavailable is when dealing with non-generic class factories. In object-oriented programming, clients often don’t create objects directly, but rather get their instances from a class factory—a known object in the system that clients ask to create objects they require, instead of creating them directly.* The advantage of using a class factory is that only the factory is coupled to the actual component types that provide the interfaces. The clients only know about the interfaces. When you need to switch from one service provider to another you only need to modify the factory (actually, instantiate a different type of a factory); the clients aren’t affected. When using a class factory that returns some common base type (usually
object
), you can use an explicit cast from the returned object
to the interface
type:
public interface IClassFactory { object GetObject( ); } IClassFactory factory; /* Some code to initialize the class factory */ IMyInterface obj; obj = (IMyInterface)factory.GetObject( ); obj.Method1( );
Example 3-5. Defining and using multiple interfaces
public interface IMyInterface { void Method1( ); void Method2( ); } public interface IMyOtherInterface { void Method3( ); } public class MyClass : IMyInterface,IMyOtherInterface { public void Method1( ) {...} public void Method2( ) {...} public void Method3( ) {...} } //Client-side code: IMyInterface obj1; IMyOtherInterface obj2; obj1 = new MyClass( ); obj1.Method1( ); obj2 = (IMyOtherInterface)obj1; obj2.Method3( );In all these examples that use explicit casts, you must incorporate error handling, in case the type you are trying to cast from doesn’t support the interface, and use
try
and catch
statements to handle any exceptions.
There is, however, a safer, defensive approach to explicit casting-the
as
operator.
The as
operator performs the cast if it’s legal and assigns a value to the variable. If a
cast isn’t possible, instead of throwing an exception, the as
operator assigns null
to
the interface variable. Example 3-6 shows how to use the as
operator to perform a
safe cast that doesn’t result in an exception in case of an error.
Example 3-6. Using the as operator to cast safely to the desired interface
SomeType obj1; IMyInterface obj2; /* Some code to initialize obj1 */ obj2 = obj1 as IMyInterface; if(obj2 != null) { obj.Method1( ); } else { //Handle error in expected interface }
as
operator as shown in Example 3-6, instead of explicit casting. Never assume an
object supports an interface—that leads both to robust error handling and to separation
of the interface from the implementation, regardless of whether or not the server
is using explicit interface implementation. Make it a habit on the client side to use
the server via an interface and thus enforce the separation manually.Interface Methods, Properties, and Events
An interface isn’t limited only to defining methods. An interface can also define properties, indexers, and events. Example 3-7 shows the syntax for defining all of these in an interface and the corresponding implementation.Example 3-7. An interface can define methods, properties, indexers, and events
public delegate void NumberChangedEventHandler(int number); public interface IMyInterface { void Method1( ); //A method int SomeProperty{ get; set; }//A property int this[int index]{ get; set;}//An indexer event NumberChangedEventHandler NumberChanged;//An event } public class MyClass : IMyInterface { public event NumberChangedEventHandler NumberChanged; public void Method1( ) {...} public int SomeProperty { get {...} set {...} } public int this[int index] { get {...} set {...} } }
Interfaces and Structs
An interesting use of interfaces with properties involves structs. In .NET, a struct (aStructure
in Visual Basic 2005) can’t have a base struct or a base class, because it’s a
value type. However, .NET does permit structs to implement one or more interfaces.
The reason for this is that sometimes you want to define abstract data storage, and there
are a number of possible implementations for the actual structure. By defining an interface
(preferably with properties only, but it can have methods as well), you can pass
around the interface instead of the actual struct and gain the benefits of polymorphism,
even though structs aren’t allowed to derive from a common base struct. Example 3-8
demonstrates the use of an interface (with properties only) as a base type for structs.
Example 3-8. Using an interface as a base type for structs
public interface IMyBaseStruct { int SomeNumber{ get; set; } string SomeString{ get; set; } } struct MyStruct : IMyBaseStruct { public int SomeNumber { get{...} set{...} } public string SomeString { get{...} set{...} } //Rest of the implementation } struct MyOtherStruct : IMyBaseStruct { public int SomeNumber { get{...} set{...} } public string SomeString { get{...} set{...} } //Rest of the implementation } //A method that accepts a struct, without knowing exactly the type public void DoWork(IMyBaseStruct storage) {...}
Interfaces and Partial Types
Partial types allow the component architect to define interface derivation for a class but have another developer implement it (similar to the old C++ distinction between header files and CPP files)://In App.cs public interface IMyInterface { void Method1( ); void Method2( ); } public partial class MyClass : IMyInterface {} //In MyClass.cs public partial class MyClass { public void Method1( ) {...} public void Method2( ) {...} }
With a partial class, each part of the class can choose to add interface derivation, or interface derivation and implementation:
public partial class MyClass {} public partial class MyClass : IMyInterface { public void Method1( ) {...} public void Method2( ) {...} }
However, only a single part can implement an interface member.
Implementing Multiple Interfaces
A class can derive from as many interfaces as required (see Example 3-5), but from at most one base class. When a class derives from a base class and from one or more interfaces, the base class must be listed first in the derivation chain (a requirement the compiler enforces):public interface IMyInterface {} public interface IMyOtherInterface {} public class MyBaseClass {} public class MySubClass : MyBaseClass,IMyInterface,IMyOtherInterface {}
Even such a trivial example raises a number of questions. What if both interfaces define identical methods? What are the available ways to resolve such collisions? What if the base class already derives from one or more of the interfaces?
When a class derives from two or more interfaces that define an identical method, you have two options: the first is to channel both interface methods to the same actual method implementation, and the second is to provide separate method implementations. For example, consider two interfaces that define the identical method
Method1( )
:public interface IMyInterface { void Method1( ); } public interface IMyOtherInterface { void Method1( ); }
If you want to channel both interface methods to the same method implementation,
all you have to do is derive from the interfaces and implement the method once:
public class MyClass : IMyInterface,IMyOtherInterface { public void Method1( ) {...} //Other methods and members }
Regardless of which interface the client of MyClass
chooses to use, calls to Method1( )
will be channeled to that single implementation:
IMyInterface obj1; IMyOtherInterface obj2; obj1 = new MyClass( ); obj1.Method1( ); obj2 = obj1 as IMyOtherInterface; Debug.Assert(obj2 != null); obj2.Method1( );
To provide separate implementations, use explicit interface implementation by qualifying the method implementation with the name of the interface that defines it:
public class MyClass : IMyInterface,IMyOtherInterface { void IMyInterface.Method1( ) {...} void IMyOtherInterface.Method1( ) {...} //Other methods and members }
Now, when the client calls an interface method, that interface-specific method is
called. You can even have separate explicit implementations for some of the common
methods and channel the others to the same implementation. However, as mentioned
before, for the sake of consistency it’s better to avoid mixing and matching.
If you want to both use explicit interface implementation and channel the implementation from one interface to the other, you will need to use the
this
reference to
query for the desired interface and delegate the call:public class MyClass : IMyInterface, IMOtherInterface { void IMyInterface.Method1 ( ) {...} void IMyOtherInterface.Method1 ( ) { IMyInterface myInterface = this; myInterface.Method ( ); } //Other methods and members }
Using the this
reference this way is the only way to call an explicit interface method by its own implementing class.
Interfaces and Class Hierarchies
In component-oriented programming, you focus on defining and implementing interfaces. In object-oriented programming, you model your solution by using class hierarchies. How do the two concepts interact? The answer depends on the way you override or redefine the interface methods at the different levels of the class hierarchy. Consider the code in Example 3-9, which illustrates that when defining an interface only at the root of a class hierarchy, each level must override its base-class declarations to preserve semantics.Example 3-9. Overriding an interface in a class hierarchy
using System.Diagnostics;//For the Trace class public interface ITrace { void TraceSelf( ); } public class A : ITrace { public virtual void TraceSelf( ) { Trace.WriteLine("A"); } } public class B : A { public override void TraceSelf( ) { Trace.WriteLine("B"); } } public class C : B { public override void TraceSelf( ) { Trace.WriteLine("C"); } }
In a typical class hierarchy, the topmost base class should derive from the interface, providing polymorphism with the interface to all subclasses. The topmost base class must also define all the interface members as virtual, so that subclasses can override them. Each level of the class hierarchy can override its preceding level (using the
override
inheritance qualifier), as shown in Example 3-9. When the client uses the
interface, it then gets the desired interpretation of the interface. For example, if the
client code is:
ITrace obj = new B( ); obj.TraceSelf( );
the object traces "B" to the output window, as expected.
{} |- | In C#, you are required to explicitly indicate the semantics of inheritance when you supply a method with a name and signature identical to those of a base-class method. If you wish to override the base-class method when instantiating a base-class type with a subclass reference, you need to use the
override
directive:
public class BaseClass { public virtual void TraceSelf( ) { Trace.WriteLine("BaseClass"); } } public class SubClass : BaseClass { public override void TraceSelf( ) { Trace.WriteLine("SubClass"); } } BaseClass obj = new SubClass( ); obj.TraceSelf( ); //Outputs "SubClass"
If you want to provide the base-class behavior instead, use the
new
directive, with or
without virtual, at the base class:
public class BaseClass { public virtual void TraceSelf( ) { Trace.WriteLine("BaseClass"); } } public class SubClass : BaseClass { public new void TraceSelf( ) { Trace.WriteLine("SubClass"); } } BaseClass obj = new SubClass( ); obj.TraceSelf( ); //Outputs "BaseClass"
Things are less obvious if the subclasses use the
new
inheritance qualifier. The new
modifier gives subclass behavior only when dealing with an explicit reference to a
subclass, such as:
B obj = new B( );
In all other cases, the base class implementation is used. If the code in Example 3-9 was written as:
public class A : ITrace { public virtual void TraceSelf( )//virtual is optional { Trace.WriteLine("A"); } } public class B : A { public new void TraceSelf( ) { Trace.WriteLine("B"); } } public class C : B { public new void TraceSelf( ) { Trace.WriteLine("C"); } }
then this client code:
ITrace obj = new B( ); obj.TraceSelf( );
would now trace "A" to the output window instead of "B." Note that this is exactly why the
new
inheritance modifier is available. Imagine a client that somehow depends
on the base class’s particular implementation. If a new subclass is used instead of the
base class, the new
modifier ensures that the client will get the implementation it
expects. However, this nuance makes sense only when you’re dealing with clients
that don’t use interface-based programming but rather program directly against the
objects:A obj = new B( ); obj.TraceSelf( ); //Traces "A"
You can support such clients and still provide interface-based services to the rest of
the clients. To achieve that, each class in the hierarchy can reiterate its polymorphism
with the interface by explicitly deriving from the interface (in addition to having
the base class derive from the interface). Doing so (as shown in Example 3-10)
makes the new
modifier yield the same results as the override
modifier for the interface-based clients:ITrace obj = new B( ); obj.TraceSelf( ); //Traces "B"
Note that using
virtual
at the base-class level is optional.
In general, you should use the
override
modifier, as in Example 3-9, with virtual
interface members at the topmost base class. Such code is readable and straightforward.
Code such as that in Example 3-10 makes for an interesting exercise but is
rarely of practical use.
Example 3-10. Deriving from the interface explicitly at each level of the class hierarchy
using System.Diagnostics;//For the Trace class public interface ITrace { void TraceSelf( ); } public class A : ITrace { public virtual void TraceSelf( )//virtual is optional { Trace.WriteLine("A"); } } public class B : A,ITrace { public new void TraceSelf( ) { Trace.WriteLine("B"); } } public class C : B,ITrace { public new void TraceSelf( ) { Trace.WriteLine("C"); } }
If you want to combine explicit interface implementation and class hierarchy, you should do so in a way that allows a subclass to call its base-class implementation. Because with explicit interface implementation, the implementation is private, you will need to add at the topmost base class a protected virtual method for each interface method. Only the topmost base class should explicitly implement the interface, and its implementation should call the protected virtual methods. All the subclasses should override the protected virtual methods:
public class A : ITrace { protected virtual void TraceSelf( ) { Trace.WriteLine("A"); } void ITrace.TraceSelf( ) { TraceSelf( ); } } public class B : A { protected override void TraceSelf( ) { Trace.WriteLine("B"); base.TraceSelf( ); } }