7. Interfaces

Polymorphism is available not only via inheritance (as discussed in the preceding chapter), but also via interfaces. Unlike abstract classes, interfaces cannot include any implementation. Like abstract classes, however, interfaces define a set of members that callers can rely on being implemented.

By implementing an interface, a type defines its capabilities. The interface implementation relationship is a “can do” relationship: The type can do what the interface requires an implementing type to do. The interface defines the contract between the types that implement the interface and the code that uses the interface. Types that implement interfaces must declare methods with the same signatures as the methods declared by the implemented interfaces. This chapter discusses implementing and using interfaces.

Image

Introducing Interfaces

The IFileCompression interface shown in Listing 7.1 is an example of an interface implementation.

Listing 7.1. Defining an Interface


interface IFileCompression
{
  void Compress(string targetFileName, string[] fileList);
  void Uncompress(
      string compressedFileName, string expandDirectoryName);
}

IFileCompression defines the methods a type must implement to be used in the same manner as other compression-related classes. The power of interfaces is that they grant the ability to callers to switch among implementations without modifying the calling code.

One key characteristic of an interface is that it has no implementation and no data. Method declarations in an interface have a single semicolon in place of curly braces after the header. Fields (data) cannot appear in an interface declaration. When an interface requires the derived class to have certain data, it declares a property rather than a field. Since the property does not contain any implementation as part of the interface declaration, it doesn’t reference a backing field.

The declared members of an interface describe the members that must be accessible on an implementing type. The purpose of nonpublic members is to make those members inaccessible to other code. Therefore, C# does not allow access modifiers on interface members; instead, it automatically defines them as public.


Guidelines

DO use Pascal casing for interface names, with an “I” prefix.


Polymorphism through Interfaces

Consider another example (see Listing 7.2): IListable defines the members a class needs to support in order for the ConsoleListControl class to display it. As such, any class that implements IListable can use the ConsoleListControl to display itself. The IListable interface requires a read-only property, ColumnValues.

Listing 7.2. Implementing and Using Interfaces


interface IListable
{
  // Return the value of each column in the row.
  string[] ColumnValues
  {
      get;
  }

}

public abstract class PdaItem
{
  public PdaItem(string name)
  {
      Name = name;
  }

  public virtual string Name{get;set;}
}

class Contact : PdaItem, IListable
{
    public Contact(string firstName, string lastName,
        string address, string phone) : base(null)
    {
        FirstName = firstName;
        LastName = lastName;
        Address = address;
        Phone = phone;
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }

  public string[] ColumnValues
  {
      get
      {
          return new string[]
          {
                FirstName,
                LastName,
                Phone,
                Address
          };
      }
  }


  public static string[] Headers
  {
      get
      {
          return new string[] {
              "First Name", "Last Name    ",
              "Phone       ",
              "Address                       " };
      }
  }

  // ...
}

class Publication : IListable
{
  public Publication(string title, string author, int year)
  {
      Title = title;
      Author = author;
      Year = year;
  }

  public string Title { get; set; }
  public string Author { get; set; }
  public int Year { get; set; }

  public string[] ColumnValues
  {
      get
      {
          return new string[]
          {
              Title,
              Author,
              Year.ToString()
          };
      }
  }


  public static string[] Headers
  {
      get
      {
          return new string[] {
              "Title                                ",
              "Author             ",
              "Year" };
      }
  }

  // ...
}

class Program
{
  public static void Main()
  {
      Contact[] contacts = new Contact[6];
      contacts[0] = new Contact(
          "Dick", "Traci",
          "123 Main St., Spokane, WA  99037",
          "123-123-1234");
      contacts[1] = new Contact(
          "Andrew", "Littman",
          "1417 Palmary St., Dallas, TX 55555",
          "555-123-4567");
      contacts[2] = new Contact(
          "Mary", "Hartfelt",
          "1520 Thunder Way, Elizabethton, PA 44444",
          "444-123-4567");
      contacts[3] = new Contact(
          "John", "Lindherst",
          "1 Aerial Way Dr., Monteray, NH 88888",
          "222-987-6543");
      contacts[4] = new Contact(
          "Pat", "Wilson",
          "565 Irving Dr., Parksdale, FL 22222",
          "123-456-7890");
      contacts[5] = new Contact(
          "Jane", "Doe",
          "123 Main St., Aurora, IL 66666",
          "333-345-6789");

      // Classes are cast implicitly to
      // their supported interfaces
      ConsoleListControl.List(Contact.Headers, contacts);

      Console.WriteLine();

      Publication[] publications = new Publication[3] {
          new Publication("Celebration of Discipline",
              "Richard Foster", 1978),
          new Publication("Orthodoxy",
              "G.K. Chesterton", 1908),
          new Publication(
              "The Hitchhiker's Guide to the Galaxy",
              "Douglas Adams", 1979)
          };
      ConsoleListControl.List(
          Publication.Headers, publications);
  }
}

class ConsoleListControl
{
    public static void List(string[] headers, IListable[] items)
    {
        int[] columnWidths = DisplayHeaders(headers);

        for (int count = 0; count < items.Length; count++)
        {
            string[] values = items[count].ColumnValues;
            DisplayItemRow(columnWidths, values);
        }
    }

    /// <summary>Displays the column headers</summary>
    /// <returns>Returns an array of column widths</returns>
    private static int[] DisplayHeaders(string[] headers)
    {
        // ...
    }

    private static void DisplayItemRow(
        int[] columnWidths, string[] values)
    {
        // ...
    }
}

The results of Listing 7.2 appear in Output 7.1.

Output 7.1.

First Name  Last Name      Phone         Address
Dick        Traci          123-123-1234  123 Main St., Spokane, WA 99037
Andrew      Littman        555-123-4567  1417 Palmary St., Dallas, TX 55555
Mary        Hartfelt       444-123-4567  1520 Thunder Way, Elizabethton, PA 44444
John        Lindherst      222-987-6543  1 Aerial Way Dr., Monteray, NH 88888
Pat         Wilson         123-456-7890  565 Irving Dr., Parksdale, FL 22222
Jane        Doe            333-345-6789  123 Main St., Aurora, IL 66666

Title                                  Author               Year
Celebration of Discipline              Richard Foster       1978
Orthodoxy                              G.K. Chesterton      1908
The Hitchhiker's Guide to the Galaxy   Douglas Adams        1979

In Listing 7.2, the ConsoleListControl can display seemingly unrelated classes (Contact and Publication). Any class can be displayed provided that it implements the required interface. As a result, the ConsoleListControl.List() method relies on polymorphism to appropriately display whichever set of objects it is passed. Each class has its own implementation of ColumnValues, and converting a class to IListable still allows the particular class’s implementation to be invoked.

Interface Implementation

Declaring a class to implement an interface is similar to deriving from a base class: The implemented interfaces appear in a comma-separated list along with the base class. The base class specifier (if there is one) must come first, but otherwise order is not significant. Classes can implement multiple interfaces, but may only derive directly from one base class. An example appears in Listing 7.3.

Listing 7.3. Implementing an Interface


public class Contact : PdaItem, IListable, IComparable
{
  // ...

  #region IComparable Members
  /// <summary>
  ///
  /// </summary>
  /// <param name="obj"></param>
  /// <returns>
  /// Less than zero:      This instance is less than obj.
  /// Zero                 This instance is equal to obj.
  /// Greater than zero    This instance is greater than obj.
  /// </returns>
  public int CompareTo(object obj)

  {
      int result;
      Contact contact = obj as Contact;

      if (obj == null)
      {
          // This instance is greater than obj.
          result = 1;
      }
      else if (obj != typeof(Contact))
      {
          throw new ArgumentException("obj is not a Contact");
      }
      else if( Contact.ReferenceEquals(this, obj) )
      {
          result = 0;
      }
      else
      {
          result = LastName.CompareTo(contact.LastName);
          if (result == 0)
          {
              result = FirstName.CompareTo(contact.FirstName);
          }
      }
      return result;
  }
  #endregion

  #region IListable Members
  string[] IListable.ColumnValues

  {
      get
      {
          return new string[]
          {
              FirstName,
              LastName,
              Phone,
              Address
          };
      }
  }
  #endregion
}

Once a class declares that it implements an interface, all members of the interface must be implemented. An abstract class is permitted to supply an abstract implementation of an interface member. A nonabstract implementation may throw a NotImplementedException type exception in the method body, but somehow, an implementation of the member must be supplied.

One important characteristic of interfaces is that they can never be instantiated; you cannot use new to create an interface, and therefore, interfaces do not have constructors or finalizers. Interface instances are available only by instantiating a type that implements the interface. Furthermore, interfaces cannot include static members. One key interface purpose is polymorphism, and polymorphism without an instance of the implementing type is of little value.

Each interface member behaves like an abstract method, forcing the derived class to implement the member. Therefore, it is not possible to use the abstract modifier on interface members explicitly.

When implementing an interface member in a type there are two ways to do so: explicitly or implicitly. So far we’ve seen only implicit implementations, where the type member that implements the interface member is a public member of the implementing type.

Explicit Member Implementation

Explicitly implemented methods are available only by calling through the interface itself; this is typically achieved by casting an object to the interface. For example, to call IListable.ColumnValues in Listing 7.4, you must first cast the contact to IListable because of ColumnValues’ explicit implementation.

Listing 7.4. Calling Explicit Interface Member Implementations


string[] values;
Contact contact1, contact2;

// ...

// ERROR:  Unable to call ColumnValues() directly
//         on a contact.
// values = contact1.ColumnValues;

// First cast to IListable.
values = ((IListable)contact2).ColumnValues;

// ...

The cast and the call to ColumnValues occur within the same statement in this case. Alternatively, you could assign contact2 to an IListable variable before calling ColumnValues.

To declare an explicit interface member implementation, prefix the member name with the interface name (see Listing 7.5).

Listing 7.5. Explicit Interface Implementation


public class Contact : PdaItem, IListable, IComparable
{
  // ...

  public int CompareTo(object obj)
  {
      // ...
  }

  #region IListable Members
  string[] IListable.ColumnValues

  {
      get
      {
          return new string[]
          {
              FirstName,
              LastName,
              Phone,
              Address
          };
      }
  }
  #endregion
}

Listing 7.5 implements ColumnValues explicitly by prefixing the property name with IListable. Furthermore, since explicit interface implementations are directly associated with the interface, there is no need to modify them with virtual, override, or public, and, in fact, these modifiers are not allowed. The method is not treated as a public member of the class, so marking it as public would be misleading.

Implicit Member Implementation

Notice that CompareTo() in Listing 7.5 does not include the IComparable prefix; it is implemented implicitly. With implicit member implementation, it is only necessary for the member to be public and for the member’s signature to match the interface member’s signature. Interface member implementation does not require the override keyword or any indication that this member is tied to the interface. Furthermore, since the member is declared just as any other class member, code that calls implicitly implemented members can do so directly, just as it would any other class member:

     result = contact1.CompareTo(contact2);

In other words, implicit member implementation does not require a cast because the member is not hidden from direct invocation on the implementing class.

Many of the modifiers disallowed on an explicit member implementation are required or are optional on an implicit implementation. For example, implicit member implementations must be public. Furthermore, virtual is optional depending on whether derived classes may override the implementation. Eliminating virtual will cause the member to behave as though it is sealed.

Explicit versus Implicit Interface Implementation

The key difference between implicit and explicit member interface implementation is not in the syntax of the method declaration, but in the ability to access the method by name through an instance of the type rather than via the interface.

When building a class hierarchy, it’s desirable to model real-world “is a” relationships—a giraffe is a mammal, for example. These are “semantic” relationships. Interfaces are often used to model “mechanism” relationships. A PdaItem “is not a” “comparable,” but it might well be IComparable. This interface has nothing to do with the semantic model; it’s a detail of the implementation mechanism. Explicit interface implementation is a technique for enabling the separation of mechanism concerns from model concerns. Forcing the caller to convert the object to an interface such as IComparable before treating the object as “comparable” explicitly separates out in the code when you are talking to the model and when you are dealing with its implementation mechanisms.

In general, it is preferable to limit the public surface area of a class to be “all model” with as little extraneous mechanism as possible. (Unfortunately, some mechanisms are unavoidable in .NET. You cannot get a giraffe’s hash code or convert a giraffe to a string. However, you can get a Giraffe’s hash code [GetHashCode()] and convert it to a string [ToString()]. By using object as a common base class, .NET mixes model code with mechanism code even if only to a limited extent.)

Here are several guidelines that will help you choose between an explicit and an implicit implementation.

• Is the member a core part of the class functionality?

Consider the ColumnValues property implementation on the Contact class. This member is not an integral part of a Contact type but a peripheral member probably accessed only by the ConsoleListControl class. As such, it doesn’t make sense for the member to be immediately visible on a Contact object, cluttering up what could potentially already be a large list of members.

Alternatively, consider the IFileCompression.Compress() member. Including an implicit Compress() implementation on a ZipCompression class is a perfectly reasonable choice, since Compress() is a core part of the ZipCompression class’s behavior, so it should be directly accessible from the ZipCompression class.

• Is the interface member name appropriate as a class member?

Consider an ITrace interface with a member called Dump() that writes out a class’s data to a trace log. Implementing Dump() implicitly on a Person or Truck class would result in confusion as to what operation the method performs. Instead, it is preferable to implement the member explicitly so that only from a data type of ITrace, where the meaning is clearer, can the Dump() method be called. Consider using an explicit implementation if a member’s purpose is unclear on the implementing class.

• Is there already a class member with the same signature?

Explicit interface member implementation does not add a named element to the type’s declaration space. Therefore, if there is already a potentially conflicting member of a type, a second one can be provided with the same name or signature as long as it is an explicit interface member.

Much of the decision regarding implicit versus explicit interface member implementation comes down to intuition. However, these questions provide suggestions about what to consider when making your choice. Since changing an implementation from implicit to explicit results in a version-breaking change, it is better to err on the side of defining interfaces explicitly, allowing them to be changed to implicit later on. Furthermore, since the decision between implicit and explicit does not have to be consistent across all interface members, defining some methods as explicit and others as implicit is fully supported.


Guidelines

AVOID implementing interface members explicitly without a good reason. However, if you’re unsure, favor explicit implementation.


Converting between the Implementing Class and Its Interfaces

Just as with a derived type and a base class, a conversion from an implementing type to its implemented interface is an implicit conversion. No cast operator is required because an instance of the implementing type will always provide all the members in the interface, and therefore, the object can always be converted successfully to the interface type.

Although the conversion will always be successful from the implementing type to the implemented interface, many different types could implement a particular interface, so you can never be certain that a “downward” cast from an interface to one of its implementing types will be successful. Therefore, converting from an interface to one of its implementing types requires an explicit cast.

Interface Inheritance

Interfaces can derive from each other, resulting in an interface that inherits all the members in its base interfaces. As shown in Listing 7.6, the interfaces directly derived from IReadableSettingsProvider are the explicit base interfaces.

Listing 7.6. Deriving One Interface from Another


interface IReadableSettingsProvider
{
    string GetSetting(string name, string defaultValue);
}

interface ISettingsProvider : IReadableSettingsProvider
{
    void SetSetting(string name, string value);
}

class FileSettingsProvider : ISettingsProvider
{
    #region ISettingsProvider Members
    public void SetSetting(string name, string value)
    {
        // ...
    }
    #endregion

    #region IReadableSettingsProvider Members
    public string GetSetting(string name, string defaultValue)
    {
        // ...
    }
    #endregion
}

In this case, ISettingsProvider derives from IReadableSettingsProvider, and therefore inherits its members. If IReadableSettingsProvider also had an explicit base interface, ISettingsProvider would inherit those members too, and the full set of interfaces in the derivation hierarchy would simply be the accumulation of base interfaces.

It is interesting to note that if GetSetting() is implemented explicitly, it must be done using IReadableSettingsProvider. The declaration with ISettingsProvider in Listing 7.7 will not compile.

Listing 7.7. Explicit Member Declaration without the Containing Interface (Failure)


// ERROR:  GetSetting() not available on ISettingsProvider
string ISettingsProvider.GetSetting(
  string name, string defaultValue)
{
  // ...
}

The results of Listing 7.7 appear in Output 7.2.

Output 7.2.

'ISettingsProvider.GetSetting' in explicit interface declaration
is not a member of interface.

This output appears in addition to an error indicating that IReadableSettingsProvider.GetSetting() is not implemented. The fully qualified interface member name used for explicit interface member implementation must reference the interface name in which it was originally declared.

Even though a class implements an interface (ISettingsProvider) which is derived from a base interface (IReadableSettingsProvider), the class can still declare an implementation of both interfaces overtly, as Listing 7.8 demonstrates.

Listing 7.8. Using a Base Interface in the Class Declaration


class FileSettingsProvider : ISettingsProvider,
     IReadableSettingsProvider

{
    #region ISettingsProvider Members
    public void SetSetting(string name, string value)
    {
        // ...
    }
    #endregion

    #region IReadableSettingsProvider Members
    public string GetSetting(string name, string defaultValue)
    {
        // ...
    }
    #endregion
}

In this listing, there is no change to the interface’s implementations on the class, and although the additional interface implementation declaration on the class header is superfluous, it can provide better readability.

The decision to provide multiple interfaces rather than just one combined interface depends largely on what the interface designer wants to require of the implementing class. By providing an IReadableSettingsProvider interface, the designer communicates that implementers are required only to implement a settings provider that retrieves settings. They do not have to be able to write to those settings. This reduces the implementation burden by not imposing the complexities of writing settings as well.

In contrast, implementing ISettingsProvider assumes that there is never a reason to have a class that can write settings without reading them. The inheritance relationship between ISettingsProvider and IReadableSettingsProvider, therefore, forces the combined total of both interfaces on the ISettingsProvider class.

One final but important note: Although inheritance is the correct term, conceptually it is more accurate to realize that an interface represents a contract; and one contract is allowed to specify that the provisions of another contract must also be followed. So, the code ISettingsProvider : IReadableSettingsProvider conceptually states that the ISettingsProvider contract requires also respecting the IReadableSettingsProvider contract rather than that the ISettingsProvider “is a kind of” IReadableSettingsProvider. That being said, the remainder of the chapter will continue using the inheritance relationship terminology in accordance with the standard C# terminology.

Multiple Interface Inheritance

Just as classes can implement multiple interfaces, interfaces can inherit from multiple interfaces; the syntax is consistent with class derivation and implementation, as shown in Listing 7.9.

Listing 7.9. Multiple Interface Inheritance


interface IReadableSettingsProvider
{
  string GetSetting(string name, string defaultValue);
}

interface IWriteableSettingsProvider
{
  void SetSetting(string name, string value);
}

interface ISettingsProvider : IReadableSettingsProvider,
    IWriteableSettingsProvider

{
}

It is unusual to have an interface with no members, but if implementing both interfaces together is predominant, it is a reasonable choice for this case. The difference between Listing 7.9 and Listing 7.6 is that it is now possible to implement IWriteableSettingsProvider without supplying any read capability. Listing 7.6’s FileSettingsProvider is unaffected, but if it used explicit member implementation, specifying which interface a member belongs to changes slightly.

Extension Methods on Interfaces

Perhaps one of the most important features of extension methods is the fact that they work with interfaces in addition to classes. The syntax is identical to that of extension methods for classes. The extended type (the first parameter and the parameter prefixed with this) is the interface that we extend. Listing 7.10 shows an extension method for IListable(). It is declared on Listable.

Listing 7.10. Interface Extension Methods


class Program
{
  public static void Main()
  {
      Contact[] contacts = new Contact[6];
      contacts[0] = new Contact(
          "Dick", "Traci",
          "123 Main St., Spokane, WA  99037",
          "123-123-1234");
       // ...

      // Classes are implicitly converted to
      // their supported interfaces
      contacts.List(Contact.Headers);


      Console.WriteLine();

      Publication[] publications = new Publication[3] {
          new Publication("Celebration of Discipline",
              "Richard Foster", 1978),
          new Publication("Orthodoxy",
              "G.K. Chesterton", 1908),
          new Publication(
              "The Hitchhiker's Guide to the Galaxy",
              "Douglas Adams", 1979)
          };
      publications.List(Publication.Headers);

  }
}

static class Listable
{
  public static void List(
      this IListable[] items, string[] headers)

  {
      int[] columnWidths = DisplayHeaders(headers);

      for (int itemCount = 0; itemCount < items.Length; itemCount++)
      {
          string[] values = items[itemCount].ColumnValues;

          DisplayItemRow(columnWidths, values);
      }
  }
  // ...
}

Notice that in this example, the extension method is not on for an IListable parameter (although it could have been), but rather an IListable[] parameter. This demonstrates that C# allows extension methods not only on an instance of a particular type, but also on a collection of those objects. Support for extension methods is the foundation on which LINQ is implemented. IEnumerable is the fundamental interface that all collections implement. By defining extension methods for IEnumerable, LINQ support was added to all collections. This radically changed programming with collections; we will explore this topic in detail in Chapter 14.

Implementing Multiple Inheritance via Interfaces

As Listing 7.3 demonstrated, a single class can implement any number of interfaces in addition to deriving from a single class. This feature provides a possible workaround for the lack of multiple inheritance support in C# classes. The process uses aggregation as described in the preceding chapter, but you can vary the structure slightly by adding an interface to the mix, as shown in Listing 7.11.

Listing 7.11. Working around Single Inheritance Using Aggregation with Interfaces


public class PdaItem
{
  // ...
}

interface IPerson
{
    string FirstName
    {
        get;
        set;
    }

    string LastName
    {
        get;
        set;
    }
}

public class Person : IPerson
{
  // ...
}

public class Contact : PdaItem, IPerson
{
  private Person Person
  {
      get { return _Person; }
      set { _Person = value; }

  }
  private Person _Person;

  public string FirstName
  {
      get { return _Person.FirstName; }
      set { _Person.FirstName = value; }

  }

  public string LastName
  {
      get { return _Person.LastName; }
      set { _Person.LastName = value; }
  }

  // ...
}

IPerson ensures that the signatures between the Person members and the same members duplicated onto Contact are consistent. The implementation is still not synonymous with multiple inheritance, however, because new members added to Person will not be added to Contact.

One possible improvement that works if the implemented members are methods (not properties) is to define interface extension methods for the additional functionality “derived” from the second base class. An extension method on IPerson could provide a method called VerifyCredentials(), for example, and all classes that implement IPerson, even an IPerson interface that had no members but just extension methods, would have a default implementation of VerifyCredentials(). What makes this a viable approach is that polymorphism is still available, as is overriding. Overriding is supported because any instance implementation of a method will take priority over an extension method with the equivalent static signature.


Guidelines

CONSIDER defining interfaces to achieve a similar effect to that of multiple inheritance.


Versioning

When creating a new version of a component or application that other developers have programmed against, you should not change interfaces. Because interfaces define a contract between the implementing class and the class using the interface, changing the interface is changing the contract, which will possibly break any code written against the interface.

Changing or removing a particular interface member signature is obviously a code-breaking change, as any call to that member will no longer compile without modification. The same is true when changing public or protected member signatures on a class. However, unlike with classes, adding members to an interface could also prevent code from compiling without additional changes. The problem is that any class implementing the interface must do so entirely, and implementations for all members must be provided. With new interface members, the compiler will require that developers add new interface members to the class implementing the interface.


Guidelines

DO NOT add members to an interface that has already shipped.


The creation of IDistributedSettingsProvider in Listing 7.12 serves as a good example of extending an interface in a version-compatible way. Imagine that at first, only the ISettingsProvider interface is defined (as it was in Listing 7.6). In the next version, however, it is determined that per-machine settings are required. To enable this, the IDistributedSettingsProvider interface is created, and it derives from ISettingsProvider.

Listing 7.12. Deriving One Interface from Another


interface IDistributedSettingsProvider : ISettingsProvider
{
  /// <summary>
  /// Get the settings for a particular machine.
  /// </summary>
  /// <param name="machineName">
  /// The machine name the setting is related to</param>
  /// <param name="name">The name of the setting</param>
  /// <param name="defaultValue">
  /// The value returned if the setting is not found.</param>
  /// <returns>The specified setting</returns>
  string GetSetting(
      string machineName, string name, string defaultValue);

  /// <summary>
  /// Set the settings for a particular machine.
  /// </summary>
  /// <param name="machineName">
  /// The machine name the setting is related to.</param>
  /// <param name="name">The name of the setting.</param>
  /// <param name="value">The value to be persisted.</param>
  /// <returns>The specified setting</returns>
  void SetSetting(
      string machineName, string name, string value);
}

The important factor is that programmers with classes that implement ISettingsProvider can choose to upgrade the implementation to include IDistributedSettingsProvider, or they can ignore it.

If instead of creating a new interface, the machine-related methods are added to ISettingsProvider, classes implementing this interface will no longer successfully compile with the new interface definition, and instead a version-breaking change will occur.

Changing interfaces during the development phase is obviously acceptable, although perhaps laborious if implemented extensively. However, once an interface is released, it should not be changed. Instead, a second interface should be created, possibly deriving from the original interface.

(Listing 7.12 includes XML comments describing the interface members, as discussed further in Chapter 9.)

Interfaces Compared with Classes

Interfaces introduce another category of data types. (They are one of the few categories of types that don’t extend System.Object.2) Unlike classes, however, interfaces can never be instantiated. An interface instance is accessible only via a reference to an object that implements the interface. It is not possible to use the new operator with an interface; therefore, interfaces cannot contain any constructors or finalizers. Furthermore, static members are not allowed on interfaces.

2. The others being pointer types and type parameter types. However, every interface type is convertible to System.Object, and it is permissible to call the methods of System.Object on any instance of an interface, so this is perhaps a hairsplitting distinction.

Interfaces are closer to abstract classes, sharing such features as the lack of instantiation capability. Table 7.1 lists additional comparisons.

Table 7.1. Comparing Abstract Classes and Interfaces

Image

Given that abstract classes and interfaces have their own sets of advantages and disadvantages, you must make a cost-benefit decision based on the comparisons in Table 7.1 and the guidelines below in order to make the right choice.


Guidelines

DO generally favor classes over interfaces. Use abstract classes to decouple contracts (what the type does) from implementation details (how the type does it.)

CONSIDER defining an interface if you need to support its functionality on types that already inherit from some other type.


Interfaces Compared with Attributes

Interfaces with no members at all, inherited or otherwise, are sometimes used to represent information about a type. For example, you might create a marker IObsolete interface to indicate that a particular type has been replaced by another type. This is generally considered to be an abuse of the interface mechanism; interfaces should be used to represent what functions a type can perform, not to indicate facts about particular types. Instead of marker interfaces, use attributes. See Chapter 17 for more details.


Guidelines

AVOID using “marker” interfaces with no members; use attributes instead.


Summary

Interfaces are a key element of object-oriented programming in C#. They provide functionality similar to abstract classes but without using up the single-inheritance option, and also support implementation of multiple interfaces.

In C#, the implementation of interfaces can be either explicit or implicit, depending on whether the implementing class is to expose an interface member directly or only via a conversion to the interface. Furthermore, the granularity of whether the implementation is explicit or implicit is at the member level: One member may be implicitly implemented while another member of the same interface is explicitly implemented.

The next chapter looks at value types and discusses the importance of defining custom value types; at the same time, the chapter points out the subtle problems that they can introduce.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.7.154