Interfaces


As mentioned earlier, by deriving from an interface a class is declaring that it implements certain functions. Because not all object-oriented languages support interfaces, this section examines C#'s implementation of interfaces in detail.

Note

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.

This section illustrates 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(); } 

This code shows that declaring an interface works syntactically in pretty much the same way as declaring an abstract class. You should be aware, however, that it is not permitted to supply implementations of any of the members of an interface. In general, an interface can only contain declarations of methods, properties, indexers, and events.

You can never instantiate an interface; it only contains the signatures of its members. An interface has neither constructors (how can you construct something that you can't instantiate?) nor 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 interfaces are usually intended to be public contracts, and having operator overloads would cause some incompatibility problems with other .NET languages, such as Visual Basic.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 implicitly public, and cannot be declared as virtual or static. That's up to implementing classes to decide. It is therefore fine for implementing classes to declare access modifiers, as is done in the example in this section.

Take for example IDisposable. If a class wants to declare publicly that it implements the Dispose() method, it must implement IDisposable — which in C# terms means that the class derives from IDisposable.

 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 } 

In this example, if SomeClass derives from IDisposable but doesn't contain a Dispose() implementation with the exact same signature as defined in IDisposable, you get a compilation error because the class would be breaking its agreed contract to implement IDisposable. Of course, there's no problem for the compiler about a class having a Dispose() method but not deriving from IDisposable. The problem, then, would be that other code would have no way of recognizing that SomeClass has agreed to support the IDisposable features.

Note

IDisposable is a relatively simple interface because it defines only one method. Most interfaces will contain more members.

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 an interface called System.Collections.IEnumerable. If it does, the C# compiler will inject IL code, which uses the methods on this interface to iterate through the members of the collection. If it doesn't, foreach will raise an exception. The IEnumerable interface is examined in more detail in Chapter 9, "Collections." It's worth pointing out that both IEnumerable and IDisposable are somewhat special interfaces to the extent that they are actually recognized by the C# compiler, which takes account of these interfaces in the code that it generates. Obviously, any interfaces that you define yourself won't be so privileged!

Defining and Implementing Interfaces

This section illustrates how to define and use interfaces through developing a short program that follows the interface inheritance paradigm. The example is based on bank accounts. Assume you are writing code that will ultimately allow computerized transfers between bank accounts. And assume for this example that 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 deposit 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 the 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 just yet.

To keep things simple, you will keep all the code for the example in the same source file. Of course, if something like the example were used in real life, you could surmise that the different bank account classes would not only be compiled to different assemblies but would be hosted on different machines owned by the different banks. (How .NET assemblies hosted on different machines can communicate is explored in Chapter 29, ".NET Remoting.") That's all much too complicated for our purposes here. However, to maintain some attempt at realism, you will define different namespaces for the different companies.

To begin, you need to define the IBank interface:

 namespace Wrox.ProCSharp { 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 you know that it's an interface.

Note

Chapter 2, "C# Basics," pointed out that, 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 exceptions in which Hungarian notation is recommended.

The idea is that you 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 that they represent bank accounts by the mere 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.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. You maintain a private field, balance, and adjust this amount when money is deposited or withdrawn. You 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, you are not implementing extra properties, such as the account holder's name! In real life that would be pretty essential information, but for this example it's unnecessarily complicated.

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

 public class SaverAccount : IBankAccount 

You've declared that SaverAccount derives from one interface, IBankAccount, and you have not explicitly indicated any other base classes (which of course means that SaverAccount derives directly from System.Object). By the way, derivation from interfaces acts completely independently from derivation from classes.

Being derived from IBankAccount means that SaverAccount gets all the members of IBankAccount. But because 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 functions are of course only allowed if the class itself is abstract). For this particular example, you don't have any reason to make any of the interface functions virtual.

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

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

We won't present details of the GoldAccount class here; in the sample code, 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.

Now that you have your classes, you can test them out. You first need a couple of using statements:

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

Now you need a Main() method:

 namespace Wrox.ProCSharp { 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 (which if you download the sample, you can find in the file BankAccounts.cs) produces this output:

C:> 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 you have declared both your 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 you can only call methods that are part of this interface through these references — if you want to call any methods implemented by a class that are not part of the interface, you need to cast the reference to the appropriate type. In the example code, you were able to call 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 (put differently, the cast from any interface to System.Object is implicit). Chapter 5, "Operators and Casts," covers the syntax for how to perform casts.

Interface references can in all respects be treated like class references — but the power of an interface reference is that it can refer to any class that implements that interface. For example, this allows you 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 accounts[1] = new SomeOtherClass();    // SomeOtherClass does NOT implement // IBankAccount: WRONG!! 

This causes a compilation error similar to this:

Cannot implicitly convert type 'Wrox.ProCSharp. SomeOtherClass' 'Wrox.ProCSharp.IBankAccount' 

Derived Interfaces

It's possible for interfaces to inherit from each other in the same way that classes do. This concept is illustrated 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 { 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.

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

To illustrate ITransferBankAccount, assume 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 example simple — that won't normally be the case), so in the following code just the differences are highlighted:

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      }    }

The class can be demonstrated 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 the following output, which as you can verify shows the correct amounts have been transferred:

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




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

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