Section 3.2. Working with Interfaces


3.2. Working with Interfaces

Now that you have learned the importance of using interfaces in your component-based 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.

When you name a new interface type, you should prefix it with a capital I and capitalize the first letter of the domain term, as in IAccount, IController, ICalculator, and so on. Use the I prefix even if the domain term itself starts with an I (such as in IIDentity or IImage). .NET TRies to do away with the old Windows and C++ Hungarian naming notations (that is, prefixing a variable name with its type), but the I prefix is a direct legacy from COM, and that tradition is maintained in .NET.


3.2.1. 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 castingimplicit and explicitand 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:

[*] See the Abstract Factory design pattern in Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley).

     public interface IClassFactory     {        object GetObject(  );     }         IClassFactory factory;     /* Some code to initialize the class factory */     IMyInterface obj;     obj = (IMyInterface)factory.GetObject(  );     obj.Method1(  ); 

When using generics (where the type parameter is defined at the factory or the method level), there is no need for the cast:

     public interface IClassFactory<T>     {        T GetObject(  );     }     IClassFactory<IMyInterface> factory;     /* Some code to initialize the class factory */     IMyInterface obj;     obj = factory.GetObject(  );     obj.Method1(  ); 

Generic interfaces are discussed in more detail later in this chapter.


Another example where implicit cast is impossible is when you want to use one interface that the class implements to get a reference to another interface that the class also supports. Consider the code in Example 3-5. Even when the client uses an implicit cast to get hold of the first interface, it needs an explicit cast to obtain the second.

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;     IMyIntee obj2;            /* Some code to initialize obj1 */            obj2 = obj1 as IMyInterface;     if(obj2 != null)     {        obj.Method1(  );     }     else     {        //Handle error in expected interface     } 

Interestingly enough, using the as operator to find out 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 where the interface isn't supported.


In general, you should always program defensively on the client side, using the as operator as shown in Example 3-6, instead of explicit casting. Never assume an object supports an interfacethat 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.

3.2.2. 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 NumberChangedEventHandlet 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           {...}        }     } 

3.2.3. Interfaces and Structs

An interesting use of interfaces with properties involves structs. In .NET, a struct (a Structure 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)     {...} 

3.2.4. 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 s MyClass     {}         public partial class MyClass : IMyInterface     {        public void Method1(  )        {...}        public void Method2(  )        {...}     } 

However, only a single part can implement an interface member.

3.2.5. 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;     IMyOtherIntee 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.


3.2.6. 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 e 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");        }     } 

C# Inheritance Directives

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 " 


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.

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 e 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(  );        }     } 



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net