Interfaces

 
Chapter 3 - Object-Oriented C#
bySimon Robinsonet al.
Wrox Press 2002
  

C# supports interfaces , which act as contracts. By deriving from an interface, a class is declaring that it implements certain functions. Because not all object-oriented languages support interfaces, we will examine C#'s implementation of interfaces in some detail in this section.

Developers familiar with COM should be aware that, although conceptually C# interfaces are similar to COM interfaces, they are not the same thing. The underlying architecture is different for example, C# interfaces do not derive from IUnknown . A C# interface provides a contract stated in terms of .NET functions. Unlike a COM interface, a C# interface does not represent any kind of binary standard.

We will illustrate interfaces by presenting the complete definition of one of the interfaces that has been predefined by Microsoft, System.IDisposable . IDisposable contains one method, Dispose() , which is intended to be implemented by classes to clean up code.

   public interface IDisposable     {     void Dispose();     }   

The above code shows that declaring an interface works syntactically in pretty much the same way as declaring an abstract class, except that it is not permitted to supply implementations of any of the members of an interface. And the only things an interface can contain are declarations of methods , properties, indexers, and events.

You can't ever actually instantiate an interface; all it contains is the signatures for its members. An interface does not have constructors (how can you construct something that you can't instantiate?), or fields (because that would imply some internal implementation). An interface definition is also not allowed to contain operator overloads, though that's not because there is any problem in principle with declaring them there isn't; it is because this would cause some incompatibility problems with other .NET languages, such as VB.NET, which do not support operator overloading.

It is also not permitted to declare modifiers on the members in an interface definition. Interface members are always public, and cannot be declared as virtual or static . That's up to implementing classes to do if required, and it is therefore fine for implementing classes to declare access modifiers, as we do in the above code.

If a class wishes to declare publicly that it implements the Dispose() method, then it must implement IDisposable which in C# terms means that the class derives from IDisposable . IDisposable is a relatively simple interface, since it defines only one method. Most interfaces will contain more members.

   class SomeClass : IDisposable     {     // this class MUST contain an implementation of the     // IDisposable.Dispose() method, otherwise     // you get a compilation error     public void Dispose()     {     // implementation of Dispose() method     }     // rest of class     }   

Another good example of an interface is provided by the foreach loop in C#. In principle, the foreach loop works internally by querying the object to find out whether it implements the System.Collections.IEnumerable interface. If it does, then the runtime uses the methods on this interface to iterate through the members of the collection. If it doesn't, then foreach will raise an exception.

Defining and Implementing Interfaces

We're going to illustrate how to define and use interfaces by developing a short sample that follows the interface inheritance paradigm. The example is based on bank accounts. We assume we are writing code that will ultimately allow computerized transfers between bank accounts. There are many companies that may implement bank accounts, but they have all mutually agreed that any classes that represent bank accounts will implement an interface, IBankAccount , which exposes methods to pay in or withdraw money, and a property to return the balance. It is this interface that will allow outside code to recognize the various bank account classes implemented by different bank accounts. Although our aim is to allow the bank accounts to talk to each other to allow transfers of funds between accounts, we won't introduce that feature yet that will come later, when we look at interface inheritance.

To keep things simple, we will keep all the code for our sample in the same source file, although in reality the different bank account classes would be compiling to different assemblies, and presumably hosted on different machines. We explore in Chapter 8 how .NET assemblies hosted on different machines can communicate. However, here, to maintain some attempt at realism , we will define different namespaces for the different companies.

To start with, we need bank account to define the IBank interface:

   namespace Wrox.ProCSharp.OOCSharp.BankProtocols     {     public interface IBankAccount     {     void PayIn(decimal amount);     bool Withdraw(decimal amount);     decimal Balance     {     get;     }     }     }   

Notice the name of the interface, IBankAccount . It's a convention that an interface name traditionally starts with the letter I , so that we know that it's an interface.

In most cases, .NET usage guidelines discourage the so-called "Hungarian" notation in which names are preceded by a letter that indicates the type of object being defined. Interfaces are one of the few cases in which Hungarian notation is recommended. We'll discuss the usage guidelines at the end of Chapter 6.

The idea is that we can now write classes that represent bank accounts. These classes don't have to be related to each other in any way, they can be completely different classes. They will, however, all declare to the world that they represent bank accounts by the fact that they implement the IBankAccount interface.

Let's start off with the first class, a saver account run by the Royal Bank of Venus:

   namespace Wrox.ProCSharp.OOCSharp.VenusBank     {     public class SaverAccount : IBankAccount     {     private decimal balance;     public void PayIn(decimal amount)     {     balance += amount;     }     public bool Withdraw(decimal amount)     {     if (balance >= amount)     {     balance -= amount;     return true;     }     Console.WriteLine("Withdrawal attempt failed.");     return false;     }     public decimal Balance     {     get     {     return balance;     }     }     public override string ToString()     {     return String.Format("Venus Bank Saver: Balance = {0,6:C}", balance);     }     }     }   

It should be pretty obvious what the implementation of this class does. We maintain a private field, balance , and adjust this amount when money is paid in or withdrawn. Note that we display an error message if an attempt to withdraw money fails because there is insufficient money in the account. Notice also that, because we want to keep the code as simple as possible, we are not implementing extra properties, such as the account holder's name! In real life that would be pretty essential information, but it's unnecessary complication for our sample.

The only really interesting line in this code is the class declaration:

 public class SaverAccount : IBankAccount 

We've declared that SaverAccount derives from one interface, IBankAccount , and we have not explicitly indicated any other base classes (which of course means that SaverAccount will derive directly from System.Object ). Although we have chosen not to explicitly derive SaverAccount from any other class, we should note that derivation from interfaces acts completely independently from derivation from classes. A class will derive from one other class and can additionally inherit as many interfaces as required, like this:

 public class MyDerivedClass : MyBaseClass, IInterface1, IInterface2 

Being derived from IBankAccount means that SaverAccount gets all the members of IBankAccount . But since an interface doesn't actually implement any of its methods, SaverAccount must provide its own implementations of all of them. If any implementations are missing, you can rest assured that the compiler will complain. Recall also that the interface just indicates the presence of its members. It's up to the class to decide if it wants any of them to be virtual or abstract (though abstract is only allowed if the class itself is abstract and will be derived from). Here, we've decided that none of the interface methods should be public, though there wouldn't be any problems declaring any of them as virtual if we wished.

To illustrate how different classes can implement the same interface, we will assume the Planetary Bank of Jupiter also implements a class to represent one of its bank accounts a Gold Account.

   namespace Wrox.ProCSharp.OOCSharp.JupiterBank     {     public class GoldAccount : IBankAccount     {     // etc     }     }   

We won't present details of the GoldAccount class because in our sample it's basically identical to the implementation of SaverAccount . We stress that GoldAccount has no connection with VenusAccount , other than that they happen to implement the same interface. Normally they would be implemented differently because they are independent classes. But for our sample, it keeps things simpler if we keep the same implementation.

Now we have our classes, we can test them out. We first need a couple of using statements:

 using System;   using Wrox.ProCSharp.OOCSharp.BankProtocols;     using Wrox.ProCSharp.OOCSharp.VenusBank;     using Wrox.ProCSharp.OOCSharp.JupiterBank;   

Then we need a Main() method:

   namespace Wrox.ProCSharp.OOCSharp     {     class MainEntryPoint     {     static void Main()     {     IBankAccount venusAccount = new SaverAccount();     IBankAccount jupiterAccount = new GoldAccount();     venusAccount.PayIn(200);     venusAccount.Withdraw(100);     Console.WriteLine(venusAccount.ToString());     jupiterAccount.PayIn(500);     jupiterAccount.Withdraw(600);     jupiterAccount.Withdraw(100);     Console.WriteLine(jupiterAccount.ToString());     }     }     }   

This code ( BankAccounts.cs ) produces this output:

  BankAccounts  Venus Bank Saver: Balance = 100.00 Withdrawal attempt failed. Jupiter Bank Saver: Balance = 400.00 

The main point to notice about this code is the way that we have declared both our reference variables as IBankAccount references. This means that they can point to any instance of any class that implements this interface. It does, however, mean that we can only call methods that are part of this interface through these references if we want to call any methods implemented by a class that are not part of the interface, then we'd normally need to explicitly cast the reference to the appropriate type. In our code, we've got away with calling ToString() (not implemented by IBankAccount ) without any explicit cast, purely because ToString() is a System.Object method, so the C# compiler knows that it will be supported by any class (or to put it another way, the cast from an interface to object is implicit).

Interface references can in all respects be treated like class references but the power of them is that an interface reference can refer to any class that implements that interface. For example, this allows us to form arrays of interfaces, where each element of the array is a different class:

   IBankAccount[] accounts = new IBankAccount[2];     accounts[0] = new SaverAccount();     accounts[1] = new GoldAccount();   

Note, however, that we'd get a compiler error if we tried something like this

   accounts[1] = new SomeOtherClass();   // SomeOtherClass does NOT implement     // IBankAccount: WRONG!!   

this will cause a compilation error similar to this:

 Cannot implicitly convert type 'Wrox.ProCSharp.OOCSharp.SomeOtherClass' to 'Wrox.ProCSharp.OOCSharp.BankProtocols.IBankAccount' 

You should note that it is also fine to implicitly convert references to any class that implements an interface to references to that interface. Conversions the other way round must be done explicitly.

Interface Inheritance

It's possible for interfaces to inherit from each other in the same way that classes do. We'll illustrate this concept by defining a new interface, ITransferBankAccount , which has the same features as IBankAccount , but also defines a method to transfer money directly to a different account:

   namespace Wrox.ProCSharp.OOCSharp.BankProtocols     {     public interface ITransferBankAccount : IBankAccount     {     bool TransferTo(IBankAccount destination, decimal amount);     }     }   

Because ITransferBankAccount derives from IBankAccount , it gets all the members of IBankAccount as well as its own. That means that any class that implements (derives from) ITransferBankAccount must implement all the methods of IBankAccount , as well as the new TransferTo() method defined in ITransferBankAccount . Failure to implement all of these methods will result in a compilation error.

One point to notice about the TransferTo() method is that it uses an IBankAccount interface reference for the destination account. This illustrates the usefulness of interfaces: when implementing and then invoking this method, we don't need to know anything about what type of object we are transferring money to all we need to know is that this object implements IBankAccount .

We'll illustrate ITransferBankAccount by assuming that the Planetary Bank of Jupiter also offers a current account. Most of the implementation of the CurrentAccount class is identical to the implementations of SaverAccount and GoldAccount (again this is just in order to keep this sample simple that won't normally be the case), so in the following code we've just highlighted the differences:

   public class CurrentAccount : ITransferBankAccount   {    private decimal balance;    public void PayIn(decimal amount)    {       balance += amount;    }    public bool Withdraw(decimal amount)    {       if (balance >= amount)       {          balance -= amount;          return true;       }       Console.WriteLine("Withdrawal attempt failed.");       return false;    }    public decimal Balance    {       get       {          return balance;       }    }   public bool TransferTo(IBankAccount destination, decimal amount)     {     bool result;     if ((result = Withdraw(amount)) == true)     destination.PayIn(amount);     return result;     }   public override string ToString()    {   return String.Format("Jupiter Bank Current Account: Balance = {0,6:C}",     balance);   }    } 

We can demonstrate the class with this code:

   static void Main()     {     IBankAccount venusAccount = new SaverAccount();     ITransferBankAccount jupiterAccount = new CurrentAccount();     venusAccount.PayIn(200);     jupiterAccount.PayIn(500);     jupiterAccount.TransferTo(venusAccount, 100);     Console.WriteLine(venusAccount.ToString());     Console.WriteLine(jupiterAccount.ToString());     }   

This code ( CurrentAccount.cs ) produces this output, which as you can verify shows the correct amounts have been transferred:

  CurrentAccount  Venus Bank Saver: Balance = 300.00 Jupiter Bank Current Account: Balance = 400.00 
  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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