Interfaces

for RuBoard

Interface is a very fundamental concept in computer programming. A large system is inevitably decomposed into parts, and it is critical to precisely specify the interfaces between these parts. Interfaces should be quite stable, as changing an interface affects multiple parts of the system. In C# interface is a keyword and has a very precise meaning. An interface is a reference type, similar to an abstract class, that specifies behavior as a set of methods , properties, indexers, and events. [2] An interface is a contract. When a class or struct implements an interface, it must adhere to the contract.

[2] We discuss events later in this chapter.

Interfaces are a useful way to partition functionality. You should first specify interfaces and then design appropriate classes to implement the interfaces. While a class in C# can inherit from only one other class, it can implement multiple interfaces.

Interfaces facilitate dynamic programs ”you can query a class at runtime to see whether it supports a particular interface, and take action accordingly . Interfaces in C# and .NET are conceptually very similar to interfaces in Microsoft's Component Object Model, but as we will see, they are much easier to work with.

In this section we will study the fundamentals of interfaces and provide illustrations using some small sample programs. Then we will restructure our Acme case study to take advantage of interfaces and explore their use in detail. After that we will examine several important generic interfaces in the .NET library, which will help us gain an understanding of how C# and the .NET library support each other to help us develop powerful and useful programs.

Interface Fundamentals

Object-oriented programming is a useful paradigm for helping to design and implement large systems. Using classes helps us to achieve abstraction and encapsulation. Classes are a natural decomposition of a large system into manageable parts. Inheritance adds another tool for structuring our system, enabling us to factor out common parts into base classes, helping us to accomplish greater code reuse.

The main purpose of an interface is to specify a contract independently of implementation. It is important to understand that conceptually the interfaces come first .

Interfaces in C#

In C# interface is a keyword, and you define an interface in a manner similar to defining a class. Like classes, interfaces are reference types. The big difference is that there is no implementation code in an interface; it is pure specification. Also note that an interface can have properties as well as methods (it could also have other members , such as indexers). As a naming convention, interface names usually begin with a capital I.

The IAccount interface specifies operations to be performed on a bank account.

 interface IAccount  {     void Deposit(decimal amount);     void Withdraw(decimal amount);     decimal Balance {get;}     void Show();  } 

This interface illustrates the syntax for declaring the read-only Balance property ”you specify the data type, the property name , and in curly brackets which of set and get apply (only get in this case, because the property is read-only).

Implementing an Interface

In C# you specify that a class or struct implements an interface by using the colon notation that is employed for class inheritance. A class can also inherit both from a class and from an interface. In this case the base class should appear first in the derivation list after the colon .

 public class AccountC : Account, IAccount  {     public void Show()  [3]  {        Console.WriteLine("balance = {0}", Balance);     }  } 

[3] Note that we do not need the override keyword when our class implements the Show method of the IAccount interface. Unlike overriding a virtual method in a class, we are implementing a method which was only specified but not implemented in the interface definition.

In our example the class AccountC inherits from the class Account , and it implements the interface IAccount . The methods of the interface must all be implemented by Account , either directly or in one of the base classes in its inheritance hierarchy.

We will examine a full-blown example of interfaces with the reservation-broker inheritance hierarchy later in the chapter, when we implement Step 2 of the case study.

As a small example, consider the program InterfaceDemo . The interface IAccount is defined, and two different classes, AccountC and AccountW , implement the interface. These implementations differ only in the Show method. The AccountC implementation performs console output to display the account balance, and AccountW uses a Windows message box. [4] The Deposit and Withdraw methods and the Balance property are all implemented in the Account base class.

[4] We will discuss Windows programming in Chapter 6. The example program has all needed references to libraries, and all you need to do to display a message box is to call the Show method of the MessageBox class.

 // Account.cs  using System;  using System.Windows.Forms;  interface IAccount  {     void Deposit(decimal amount);     void Withdraw(decimal amount);     decimal Balance {get;}     void Show();  }  public class Account  {     private decimal balance;     public Account()     {        balance = 100;     }     public void Deposit(decimal amount)     {        balance += amount;     }     public void Withdraw(decimal amount)     {        balance -= amount;     }     public decimal Balance     {        get        {           return balance;        }     }  }  public class AccountC : Account, IAccount  {     public void Show()     {        Console.WriteLine("balance = {0}", Balance);     }  }  public class AccountW : Account, IAccount  {     public void Show()     {        MessageBox.Show("balance = " + Balance);     }  } 
Using an Interface

You may call methods of an interface through an object reference to the class, or you may obtain an interface reference and call the methods through this interface reference. [5] The test program in the file InterfaceDemo.cs demonstrates both. We obtain the interface reference iacc by an implicit cast when we do the assignment to the object reference acc or accw . Note the polymorphic behavior of the call to Show , using console or Windows output depending on which object is being used.

[5] As we will see later in the chapter when we discuss "explicit interface implementation," you can force a client program to use an interface reference and not a class reference.

 // InterfaceDemo.cs  using System;  class InterfaceDemo  {     public static void Main()     {        // Use an object reference        AccountC acc = new AccountC();        acc.Deposit(25);        acc.Show();        // Use an interface reference  IAccount iacc = acc;  iacc.Withdraw(50);  iacc.Show();  // Use interface reference for another class        // that implements IAccount        AccountW accw = new AccountW();  iacc = accw;   iacc.Show();  }  } 

Multiple Interfaces

Our first example illustrated two classes providing different implementations of the same interface. Another common scenario is for a class to implement multiple interfaces, and in C# it is easy to test at runtime which interfaces are implemented by a class.

Our example program is MultipleInterfaces , which also illustrates interface inheritance. The interfaces IBasicAccount , IDisplay , and IAccount are defined in the file AccountDefs.cs .

 // AccountDefs.cs  interface IBasicAccount  {     void Deposit(decimal amount);     void Withdraw(decimal amount);     decimal Balance {get;}  }  interface IDisplay  {     void Show();  }  interface IAccount : IBasicAccount, IDisplay   {   }  
Interface Inheritance

Interfaces can inherit from other interfaces. Unlike classes in C#, for which there is only single inheritance, there can be multiple inheritance of interfaces. In our example, the interface IAccount is declared by inheriting from the two smaller interfaces, IBasicAccount and IDisplay . The advantage of factoring the original interface into two smaller interfaces is an increase in flexibility. For example, a class implementing IBasicAccount may run on a server, where it would not be appropriate to implement IDisplay .

When declaring a new interface using interface inheritance, you can also introduce additional methods, as illustrated for IAccount2 .

 interface IAccount2 : IBasicAccount, IDisplay  {     void NewMethod();  } 
Implementing Multiple Interfaces

A class implements multiple interfaces by mentioning each interface in its inheritance list and by providing code for the methods of each interface. A method may be implemented through inheritance from a base class. The file Account.cs in the MultipleInterfaces project illustrates two classes. BasicAccount implements only the interface IBasicAccount , and Account implements the two interfaces, IBasicAccount and IDisplay .

 // Account.cs  using System;  public class BasicAccount : IBasicAccount  {     private decimal balance;     public BasicAccount()     {        balance = 100;     }     public void Deposit(decimal amount)     {        balance += amount;     } 
 public void Withdraw(decimal amount)     {        balance -= amount;     }     public decimal Balance     {        get        {           return balance;        }     }  }  public class Account : BasicAccount, IBasicAccount,   IDisplay  {     public void Show()     {        Console.WriteLine("balance = {0}", Balance);     }  } 
Using Multiple Interfaces

The test program MultipleInterfaces.cs illustrates using (or trying to use) the two interfaces with an Account object and a BasicAccount object. Both interfaces can be used with Account , but we cannot use the IDisplay interface with BasicAccount . If we attempted to do an implicit cast from BasicAccount to IDisplay , the compiler would flag an error message. In our code we perform an explicit cast within a try block. The code compiles, but we get a runtime InvalidCast exception, which we catch. The program also illustrates that we can sometimes take a reasonable, alternative course of action if the desired interface is not available. In our case, we are able to perform the output ourselves , making use of the Balance property of the IBasicAccount interface.

 // MultipleInterfaces.cs  using System;  class MultipleInterfaces  {     public static void Main()     {        IBasicAccount iacc;        IDisplay idisp;        // Use an Account object, which has full functionality        Account acc = new Account();        iacc = acc;        idisp = acc;        iacc.Deposit(25);        idisp.Show();        // Use BasicAccount object, with reduced functionality        BasicAccount bacc = new BasicAccount();        iacc = bacc;        iacc.Withdraw(50);  try   {   idisp = (IDisplay) bacc;   idisp.Show();   }   catch (InvalidCastException e)   {   Console.WriteLine("IDisplay is not supported");   Console.WriteLine(e.Message);   // Display the balance another way   Console.WriteLine("balance = {0}", iacc.Balance);   }  }  } 

Here is the output from running the program:

 balance = 125  IDisplay is not supported  Exception of type System.InvalidCastException was thrown.  balance = 50 

Dynamic Use of Interfaces

A powerful feature of interfaces is their use in dynamic scenarios, allowing us to write general code that can test whether an interface is supported by a class. If the interface is supported, our code can take advantage of it; otherwise our program can ignore the interface. We could in fact implement such dynamic behavior through exception handling, as illustrated previously. Although entirely feasible , this approach is very cumbersome and would lead to programs that are hard to read. C# provides two operators, as and is , that facilitate working with interfaces at runtime.

As an example, consider the program DynamicInterfaces , which uses the interface definitions and class implementations from our previous example. The test program illustrates using each of the C# as and is operators to check whether the IDisplay interface is supported.

 // DynamicInterfaces.cs  using System;  class DynamicInterfaces  {     public static void Main()     {        IBasicAccount iacc;        IDisplay idisp;        BasicAccount bacc = new BasicAccount();        iacc = bacc;        iacc.Withdraw(50);        // Check IDisplay via C# "as" operator  idisp = bacc as IDisplay;   if (idisp != null)  idisp.Show();        else        {           Console.WriteLine("IDisplay is not supported");           // Display the balance another way           Console.WriteLine("balance = {0}", iacc.Balance);        }        // Check IDisplay via C# "is" operator  if (bacc is IDisplay)  {  idisp = (IDisplay) bacc;  idisp.Show();        }        else        {           Console.WriteLine("IDisplay is not supported");           // Display the balance another way           Console.WriteLine("balance = {0}", iacc.Balance);        }     }  } 

Here is the output from running the test program:

 IDisplay is not supported  balance = 50  IDisplay is not supported  balance = 50 
As Operator [6]

[6] The C# as operator is similar to dynamic_cast in C++.

The as operator is used to convert one reference type to another reference type. A common application is to convert an object reference or an interface reference to another interface reference. Unlike performing the conversion by a cast operation, the as operator never throws an exception. If the conversion fails, the result value is null .

 idisp = bacc  as  IDisplay;  if (idisp != null)     // idisp is a valid interface reference 

The as operator can also be used to explicitly convert a value type to a reference type by a boxing operation. Again, null is returned if the conversion fails.

Is Operator [7]

[7] The C# is operator is similar to type_id in C++.

The is operator dynamically checks if the runtime type of an object is compatible with a given type. The result is a boolean value. The is operator can be used to check if an object refers to a class supporting a given interface, as illustrated in our DynamicInterfaces program.

  if (bacc is IDisplay)  {  idisp = (IDisplay) bacc;  idisp.Show();  } 

The is operator is not the most efficient solution, as a check of the type is made twice . The first time is when the is operator is invoked. But the check is made all over again when the cast operation is performed, because the runtime will throw an exception if the interface is not supported. For this situation, as is more efficient, since you obtain the interface reference directly.

The is operator is useful if you want to check whether an interface is supported but you don't need to directly call a method of the interface. Later in the chapter we will see an example of this situation, when we discuss the IComparable interface. If the elements of a collection support IComparable , you will be able to call a Sort method on the collection. The Sort method calls the CompareTo method of IComparable , although your own code does not.

Interfaces in C# and COM

There are many similarities between .NET and COM. In both, the concept of interface plays a fundamental role. Interfaces are useful for specifying contracts. Interfaces support a very dynamic style of programming.

In COM you must yourself provide a very elaborate infrastructure in order to implement a COM component. You must implement a class factory for the creation of COM objects. You must implement the QueryInterface method of IUnknown for the dynamic checking of interfaces. You must implement AddRef and Release for proper memory management.

With C# (and other .NET languages) the Common Language Runtime does all this for you automatically. You create an object via new . You check for an interface via is or as and obtain the interface by a cast. The garbage collector takes care of memory management for you.

Explicit Interface Implementation

When working with interfaces, an ambiguity can arise if a class implements two interfaces and each has a method with the same name and signature. As an example, consider the following versions of the interfaces IAccount and IStatement . Each interface contains the method Show .

 interface IAccount  {     void Deposit(decimal amount);     void Withdraw(decimal amount);     decimal Balance {get;}  void Show();  }  interface IStatement  {     int Transactions {get;}  void Show();  } 

How can the class specify implementations of these methods? The answer is to use the interface name to qualify the method, as illustrated in the program Ambiguous . The IAccount version IAccount.Show will display only the balance, and IStatement.Show will display both the number of transactions and the balance.

 // Account.cs (project "Ambiguous")  ...  public class Account : IAccount, IStatement  {     private decimal balance;     int numXact = 0;     public Account(decimal balance)     {        this.balance = balance;     }     public void Deposit(decimal amount)     {        balance += amount;        ++numXact;     }     public void Withdraw(decimal amount)     {        balance -= amount;        ++numXact;     }     public decimal Balance     {        get        {           return balance;        }     }  void IAccount.Show()   {   Console.WriteLine("balance = {0}", balance);   }  public int Transactions     {        get        {           return numXact;        }     }  void IStatement.Show()   {   Console.WriteLine("{0} transactions, balance = {1}",   numXact, balance);   }  } 

You will notice that in the definition of the class Account , the qualified methods IAccount.Show and IStatement.Show do not have an access modifier such as public . Such qualified methods cannot be accessed through a reference to a class instance. They can only be accessed through an interface reference of the type explicitly shown in the method definition. The test program shows that we cannot call the IAccount.Show method through an Account object reference but only through an IAccount interface reference.

By obtaining an IStatement interface reference, we can call IStatement.Show .

 // Ambiguous.cs  using System;  public class Ambiguous  {     public static void Main()     {        Account acc = new Account(100);  // acc.Show(); // illegal - MUST go through an   // interface   IAccount iacc = (IAccount) acc;   IStatement istat = (IStatement) acc;   iacc.Show();   istat.Show();  iacc.Deposit(25);        iacc.Withdraw(10);  iacc.Show();   istat.Show();  }  } 

Even when there is no ambiguity, you may wish to use explicit interface implementation, in order to force client programs to use interfaces to call the methods specified in the interfaces. This approach makes it very clear that the client code is programming against specific interfaces and not against a large amorphous collection of methods of a class. The code will be easily adaptable to using different classes that implement the same interfaces.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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