Interface-Based Programming


Now we focus on how to program interfaces in C# so that classes can implement the members of various interfaces. Implementation inheritance and interface inheritance can be combined as stipulated earlier in the chapter. There is an accepted format for declaring interfaces. You can extend the implementation of the SupplyTeacher class by defining two interfaces, which contain methods that the SupplyTeacher class must implement, as the interface declarations will only contain a method signature and no implementation.

The SupplyTeacher class inherits from Teacher and implements ITemporaryWorker and IPublicWorker interfaces. The interface names should always be preceded by I. This means that the SupplyTeacher class derives from the Teacher class, so it is really a type of Teacher with different features from a StaffTeacher. The ordering in the code generally begins with inheritance of the base class and is followed by a list of interfaces being implemented.

    public class SupplyTeacher : Teacher, ITemporaryWorker, IPublicWorker 

Interfaces can aid development tasks, as there is no limit to the number of interfaces that any of our classes can implement (unlike implementation inheritance). With interface inheritance we can decouple our concrete class from calling code so that there are no dependencies on the actual class in code. If the interface changes then the class implementation will change to insist that new implementations be revised in the concrete classes. A struct can implement an interface, but it cannot inherit implementation from a class.

The SupplyTeacher class supports two interfaces: ITemporaryWorker and IPublicWorker. By implementing these interfaces you can treat the class in the same manner by using substitutability. This is important since this class can be considered as a kind of ITemporaryWorker and IPublicWorker. This means that the SupplyTeacher class must implement all the methods specified on each of these interfaces, and can also be treated as each of these interfaces without actually needing to know the type of class. It must implement all the methods that each interface defines, since this is a contract between the class and the calling code, else the compiler will generate an exception.

In OO development the correct way to use multiple inheritance is to inherit multiple interfaces; this is the programming model supported by both .NET and Java. Interfaces can be used to complement the use of implementation inheritance. You may want to implement an interface on the Teacher class since a Teacher is a type of PublicSectorWorker and shares certain things in common with other public sector workers. By default all the derived classes would inherit the common interface that should be shared by all public sector workers, as each derived class could be treated as a type of PublicSectorWorker too. The following examples will extend the Teacher class model to enable the implementation of interfaces, which allow polymorphism to be used on the classes.

Defining an Interface

Creating a base class for all the public sector workers is not a good idea since it would fail the is a rule. Use interfaces to ensure that all the types of public services worker class implement the Load() method. We can define an interface called INonAdminPublicSectorEmployee that includes the Load() method in its definition. This means that all of the classes that implement this interface will have to implement the same signature method.

We define an interface below that supports two methods, the Load() method that takes a string argument and the GetItem() method, which takes a DateTime argument and returns a string. Note that there is no implementation in the definition. The interface cannot contain any implementation code, it can only contain signatures for methods, events, and properties (it cannot contain static members or instance members either!).

Each class must implement the two methods defined here in some way, else the C# compiler would generate an exception (unless the methods are abstract).

    public interface INonAdminPublicSectorWorker    {      void Load(string fileName);      string GetItem(DateTime date);    } 

Implementing the Interface

You can add a HospitalWorker class to the current class hierarchy for teachers, which will have a custom implementation of the INonAdminPublicSectorWorker interface methods. A simple implementation for the Load() method by the HospitalWorker() class is shown overleaf, which uses a comma-delimited file containing all of the date information for a particular day that relates to appointment entries. In this example, there is no prefix to the name of the method to register that it is an implementation of the interface signature since the compiler automatically knows that the method being implemented has the same signature as the one specified on the interface.

The runtime uses the interface map to determine the interfaces supported by the class and will check to see whether the supported method signature is present. The method table can check the interface map for interfaces supported by the class that will have knowledge of the class method positions in the vtable, which are stored sequentially per interface. This adds another level of indirection to using interfaces.

The Load() method will use the date and time values to create a DateTime object, which can be used as a key for the Hashtable, which will store all the relevant string values against the key. The second method GetItem() is also implemented, which takes a DateTime value using it as a key to the Hashtable and returns the appropriate value or returns an empty string if the DateTime() doesn't exist in the collection. Note that the implementation in the other implementation classes can take any form. The following code file is saved as implementing_interfaces.cs:

    public class HospitalWorker : INonAdminPublicSectorWorker    {       private Hashtable ht = new Hashtable();       public void Load(string FileName)       {          try          {             FileStream stream = File.Open(FileName, FileMode.Open,                                            FileAccess.Read);             StreamReader reader = new StreamReader(stream);             string fileContents = reader.ReadToEnd();             foreach(string line in                     fileContents.Split(Environment.NewLine.ToCharArray()))             {                     string[] agenda = line.Split(new Char[] { ',' });                     ht.Add(DateTime.Parse(agenda[0]), agenda[1]);             }          }             catch{}             finally             {                if(reader!=null)                   {reader.Close();}             }    static void Main(string[] args)    {      HospitalWorker hw = new HospitalWorker();      hw.Load("C:\\agenda.txt");    }    }     public string GetItem(DateTime dt)     {       return ht[dt].ToString ();     }    } 

Polymorphism with Interfaces

We don't need to know whether the implementation class is a HospitalWorker, a Teacher, or any other class that implements the INonAdminPublicSectorWorker interface. We can change the class creation statement and the invocation. Then you simply need to know that the interface is an INonAdminPublicSectorWorker type. As a result, we have implemented the same level of abstraction in this example through polymorphism, as we did within the concrete base-class implementation, where the CLR automatically invoked the correct method on the derived class even though we used the more generic Teacher type in code.

    INonAdminPublicSectorWorker hw = new HospitalWorker();    hw.Load("C:\\agenda.txt"); 

By using the interface with distinct types of classes we have an idea of the subset of functionality that each class offers; by using the INonAdminPublicSectorWorker interface in place of the class in the code example above we will only see the supporting methods that the interface supports as opposed to the class. This is the public signature between the classes, and the contract that binds the calling code and the concrete class.

As interfaces don't contain any implementation code, they cannot be instantiated like abstract classes, though they can be considered as a grouping mechanism for related classes. This is why polymorphism works with interfaces. For example, we could up-cast the HospitalWorker object to the interface type that it implements using the following code. When the class implements many interfaces it can be (up) cast to any one of those interfaces in code. Calling the Load() method on the inaps reference will invoke the required method on the HospitalWorker class instance as seen above.

    HospitalWorker hw = new HospitalWorker();    INonAdminPublicSectorWorker inaps = (INonAdminPublicSectorWorker)hw;    inaps.Load("C:\\agenda.txt"); 

Be wary of using the interface type instead of the class type, as we can't invoke anything that is unsupported by the interface; this behavior is identical to the usage of the base-class type to invoke methods on the derived-class instance. For example, this would be illegal usage of interface-based programming since the INonAdminPublicSectorWorker doesn't support the ReturnEverything() method, which is a method supported by the HospitalWorker class:

    inaps.ReturnEverything(); 

The way to get round this issue is to down-cast again to a HospitalWorker type, which has a different interface defined by the extra method ReturnEverything().

    if(inaps is HospitalWorker) {        HopsitalWorker hw = (HospitalWorker)inaps; } 

We extend the model to define a method, which will use the interface type instead of the concrete type and invoke a method supported by that interface. This really emphasizes the benefits of using polymorphism in code providing a deep level of abstraction. We can write a static method using the base-derived class model passing in the interface type, instead of the concrete class and the filename of the agenda file, which would pass to the Load() method. It is irrelevant what concrete implementation class is used as long as it supports the correct interface.

    public static void InvokeMemberOnNonAdmin(INonAdminPublicSectorWorker    inon, string fileName)    {      try        {          inon.Load(fileName);        }      catch(Exception ex)      {        //log this exception to a file      }    } 

To invoke this we would use code similar to the earlier example. (Though try to avoid using such long method names in your code!)

    INonAdminPublicSectorWorker hw = new HospitalWorker();    InvocationClass. InvokeMemberOnNonAdmin( hw, "C:\\agenda.txt"); 

We can have many interfaces implemented by the same class, and we can have many diverse classes that implement the same interface. This idea of having many interfaces being implemented by a single class is commonly known as multiple interface inheritance. When we talk implementation inheritance we normally say that we inherit from the base class whereas the equivalent usage of words for an interface suggests that we implement an interface.

This idea of a mix and match of interfaces can be used throughout our own class libraries, just as it has been with the .NET Framework; for example we could just as easily develop classes that implement common interfaces within the .NET Framework such as IList, ICollection, ICloneable, IFormattable, etc.

An example already seen is the implementation of a class that supports the IComparer interface. This class can be passed to the Sort() method of a collection class object, which will call the implemented Compare() method several times, to compare all the members of the collection. It allows us to use .NET Framework collection classes, rather than build our own, and specify the sort class that we want to use. The possibilities for multiple interface inheritance are evident because the same class can be used in a number of different ways using polymorphism with respect to many supported interfaces.

Multiple Interface Inheritance

The code below defines an ILazyWorker interface, which supports a single method called WastedTime() whose implementation is down to the implementation class. Interfaces can be declared as either public or internal, the default one being public.

    interface ILazyWorker    {      int WastedTime();    } 

We can alter the definition of the HospitalWorker class to support both the INonAdminPublicSectorWorker interface and the new ILazyWorker interface. There is no limit to the number of interfaces that an object can support but most interfaces will support few methods to keep their footprint low.

    public class HospitalWorker : INonAdminPublicSectorWorker, ILazyWorker 

The HospitalWorker supporting the two interfaces listed above means that there is a contract between the code that creates instances of classes, and the classes themselves. The object creation code knows what the object should support. This enables us to assert what the object can do based on the definition of the interface rather than actually knowing what the type of object is. This is how polymorphism is enforced through the contractual obligations of the implementation class.

click to expand
Figure 11

Interfaces in IL

Let's look at the MSIL for the ILazyWorker interface. The interface instruction allows an entry to be added to the interface map, and the abstract instruction specifies that the interface cannot be instantiated (just like abstract classes).

click to expand

Viewing the MSIL we find that each method uses the newslot instruction, since there are no virtual implementations of this method in this interface. We are not overriding anything with this definition, and everything will be added to the virtual methods section of the interface. Each method is declared as abstract even though the definition is omitted from our C# code. We have defined a contract with the implementation of the interface exactly the same way as the implementation of an abstract method. The author of the implementing class must implement all the methods in an interface so every method on an interface is defined as abstract:

click to expand

We assess the MSIL output of the implementation class HospitalWorker. Java programmers should be familiar with the syntax used to denote interface inheritance in MSIL – the implements instruction tells the CLR to add these interfaces to the interface map and check that the interface methods are being implemented by the concrete class.

click to expand

Two implemented methods of INonAdminPublicSectorWorker and ILazyWorker within the HospitalWorker class are shown opposite in MSIL. The MSIL instruction implementation shows that the method is declared using newslot, as there is no virtual implementation within the interface. Also note that this method is declared as final which means that we cannot override it in a derived class. We could circumvent this by declaring the method as virtual in the concrete class, which would remove the final instruction and allow it to be overridden in a derived class.

click to expand

All methods implemented in the concrete HospitalWorker class are made publicly visible in the above code examples. The visibility can be varied by providing private definitions for the interface methods. Private members are invoked if we use an interface type reference, rather than the reference type of the concrete class.

Scoping Interface Methods

Let's reiterate the ILazyWorker interface. Notice how an access modifier is not used.

    interface ILazyWorker    {      int WastedTime();    } 

The concrete implementation of this interface, the HospitalWorker class has a public method declared to implement the WastedTime() method of the ILazyWorker interface.

    public int WastedTime()    {      return -1;    } 

We could also declare the ILazyWorker.WastedTime() method without an access modifier, which will automatically make the visibility of the method private, as all methods default to being privately visible in a class unless otherwise modified.

Thus, we can have methods for the concrete class and the interface, which would enable different methods to be invoked. The public method returns a strongly typed class instance, whereas the private method returns an object that will have to be (down) cast. Another use is that multiple interfaces can avoid method name and signature collision. This principle asserts that if two interfaces have a method with the same name and signature, their implementations will coexist in the same concrete class without any ambiguity.

This is achieved through the use of pointers, from the interface map back to the vtable, which will have either privately scoped, or public methods at the same offset in the method table for each contiguous interface method map. The determinant will be the type of reference used to invoke the method, which is determined from the RuntimeTypeHandle. If we had a reciprocal WastedTime() method on the INonAdminPublicWorker interface, then it would exist in isolation only visible through the interface reference. Try using this approach when implementing many interfaces against a single concrete class, as polymorphism through interface implementation avoids name collision.

    int ILazyWorker.WastedTime()    {        return -2;    } 

We use the following code to obtain a concrete class type reference to the HospitalWorker class and invoke the WastedTime() method, and then reference the ILazyWorker interface.

    HospitalWorker h = new HospitalWorker();    Console.WriteLine(h.WastedTime());    ILazyWorker ilw = new HospitalWorker();    Console.WriteLine(ilw.WastedTime()); 

The methods declared in the implementation class are stored within the method table, in the same way as virtual, overridden, or new methods are stored in a derived class (method table). The interface map is used by the CLR, and contains all the interfaces not just those supported by the current class. There are series of pointers in the table back to virtual methods, supported by each interface that the class implements.

Any cast to unsupported interfaces will not work, as there aren't any method table pointers for that method within the class. Looking up the interface map adds a second layer over and above the use of implementation inheritance, so it will result in a slightly slower implementation. Microsoft acknowledges that the JIT compiler can optimize this. The use of implementation inheritance involves a slight overhead, as the callvirt instruction is used instead of call, though the code maintainability, and the use of privately scoped interface methods compensate for this slight degradation in performance.

Casting to Different Interface Types

The idea behind polymorphism is that the class type is irrelevant to whether or not it supports the contract and can be substituted for an interface reference. The following example illustrates how to use multiple interfaces and cast them from the concrete type to the interface type. First we create a new HopspitalWorker class instance calling the WastedTime() method, which calls the public signature method since we are using the concrete class type reference. Then we cast the HospitalWorker to the supported interface ILazyWorker. We then cast the resultant ilw interface type reference field to the INonAdminPublicSectorWorker interface, since the underlying type is aware of the supported interfaces through the interface map. The compiler will filter out any unsupported interface casts from the concrete class to an interface type and generate an exception.

    HospitalWorker worker = new HospitalWorker();    Console.WriteLine(worker.WastedTime());    ILazyWorker ilw = (ILazyWorker)h;    INonAdminPublicSectorWorker inaps2 = (INonAdminPublicSectorWorker)ilw;    Console.WriteLine(inaps2.GetItem(DateTime.Now));    Console.WriteLine(ilw.WastedTime()); 

The code would produce the following output as expected:

    C:\Class Design\Ch 07>implementing_interfaces.cs    -1    -2    Not implemented! 

Both interfaces invoke the corresponding interface methods rather than the public signature methods. The MSIL for this code section is recorded below – in all instances we can see the callvirt instruction being used for every method call whether the concrete reference or the interface reference is used. We can convert between supporting interfaces due to the presence of the castclass instruction, which uses the underlying base-class type to check if the interface is supported. If it is then the cast goes ahead – this is how we can cast between interfaces without using the concrete class reference directly.

click to expand

Name Ambiguity and Shadowing

We can declare a second interface that has the same method signature as the first, and there will be no name collision since we can only have one public signature on the concrete class. The WastedTime() method would therefore be the same for both interfaces publicly. Hence, the public contract would be fulfilled since both interfaces support this method.

    interface ILazyWorker2    {       int WastedTime();    } 

To support shadowing between interfaces we can redefine the above interface with a slightly different method signature but the same method name. This enables us to introduce a new interface into the definition of the concrete class, which acts as if the WastedTime() method were overloaded on the public interface. Further, we can use this like an overloaded method and provide an implementation for the WastedTime(int) method, not the parameterless WastedTime() method. This allows the parameterless WastedTime() method to call the second method with a zero value, which doesn't affect the calculation. This is a good way of extending the implementation model while maintaining backward compatibility and providing a safe facility to overload methods in public concrete type interfaces.

    interface ILazyWorker2    {       int WastedTime(int time);    } 

We can also use inheritance in the following way between interfaces. This will just act as a declaration that is identical in operation to using multiple interface inheritance of these two interfaces. In effect the implementation class will still have to implement both ILazyWorker and ILazyWorker2.

    interface ILazyWorker : ILazyWorker2    {       int WastedTime();    } 




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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