Interfaces - Essential C# 6.0 (2016)

Essential C# 6.0 (2016)

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


Beginner Topic: Why Interfaces?

Interfaces are useful because—unlike abstract classes—they enable the complete separation of implementation details from services provided. For a real-world example, consider the “interface” that is an electrical wall socket. How the electrical power gets to the socket is an implementation detail: It might be generated by chemical, nuclear, or solar energy; the generator might be in the next room or far away; and so on. The socket provides a “contract”: It agrees to supply a particular voltage at a specific frequency, and in return it requires that the appliance using that interface provide a compatible plug. The appliance need not care anything about the implementation details that get power to the socket; all it needs to worry about is that it provides a compatible plug.

Consider the following example: A huge number of file compression formats are available (.zip, .7-zip, .cab, .lha, .tar, .tar.gz, .tar.bz2, .bh, .rar, .arj, .arc, .ace, .zoo, .gz, .bzip2, .xxe, .mime, .uue, and .yenc, just to name a few). If you created classes for each compression format, you could end up with different method signatures for each compression implementation and no ability to apply a standard calling convention across them. The desired method could be declared as abstract in the base class. However, deriving from a common base class uses up a class’s one and only opportunity for inheritance. It is unlikely that there is any code common to various compression implementations that can be put in the base class, thereby ruling out the potential benefits of having a base class implementation. The key point is that base classes let you share implementation along with the member signatures, whereas interfaces allow you to share the member signatures without the implementation.

Instead of sharing a common base class, each compression class needs to implement a common interface. Interfaces define the contract that a class supports to interact with the other classes that expect the interface. If all the classes implemented the IFileCompressioninterface and its Compress() and Uncompress() methods, the code for calling the algorithm on any particular compression class would simply involve a conversion to the IFileCompression interface and a call to the members. The result is polymorphism because each compression class has the same method signature but individual implementations of that signature.


The IFileCompression interface shown in Listing 7.1 is an example of an interface implementation. By convention—a convention so strong it is universal—the interface name is PascalCase with a capital “I” prefix.

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, as shown in Listing 7.2: IListable defines the members that a class needs to support for the ConsoleListControl class to display it. As such, any class that implements IListable can use the ConsoleListControl to display itself. TheIListable 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 derive directly from only 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.GetType() != typeof(Contact))
{
// Use C# 6.0 nameof operator in message to
// ensure consistency in the Type name.
throw new ArgumentException(
$"The parameter is not a of type { nameof(Contact) }",
nameof(obj));
}
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, so 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 has 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 them 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 ofColumnValues’ 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. 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 necessary only 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 use of the override keyword or any indication that this member is tied to the interface. Furthermore, since the member is declared just like 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 lies not in the syntax of the method declaration, but rather 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 beIComparable. 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, for example. 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 implementation 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 rather 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: 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 which 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 making regarding implicit versus explicit interface member implementation comes down to intuition. However, these questions provide suggestions about which issues 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 implementations 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; 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. As a consequence, 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 is derived from IReadableSettingsProvider and, therefore, inherits its members. If IReadableSettingsProvider also had an explicit base interface, ISettingsProvider would inherit those members as well, and the full set of interfaces in the derivation hierarchy would simply be the accumulation of base interfaces.

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) that 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. Although the additional interface implementation declaration on the class header is superfluous, it provides for 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 say 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, so interfaces can inherit from multiple interfaces. The syntax used for this purpose 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 the interface to which a member belongs changes slightly.

Begin 3.0

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 used is identical to that used for 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);
}
}
// ...
}


In this example, the extension method is not on for an IListable parameter (although it could have been), but rather for 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.

End 3.0

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 toContact.

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 calledVerifyCredentials(), 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 approach viable is the fact 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.



Beginner Topic: Interface Diagramming

Interfaces in a UML-like1 figure take two possible forms. First, you can show the interface as though it is an inheritance relationship similar to a class inheritance, as demonstrated in Figure 7.1 between IPerson and IContact. Alternatively, you can show the interface using a small circle, often referred to as a lollipop, exemplified by IPerson and IContact in Figure 7.1.

1. Unified Modeling Language (UML), a standard specification for modeling object design using graphical notation.

Image

FIGURE 7.1: Working around Single Inheritances with Aggregation and Interfaces

In Figure 7.1, Contact derives from PdaItem and implements IContact. In addition, it aggregates the Person class, which implements IPerson. Although the Visual Studio Class Designer does not support this practice, interfaces are sometimes shown as using a derivation-type arrow to a class. For example, Person could have an arrow to IPerson instead of a lollipop.


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 equivalent to 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 you change 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 constraint, 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 issue 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. 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 perhaps this is a hairsplitting distinction.

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

Image

TABLE 7.1: Comparing Abstract Classes and Interfaces

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 that follow 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 which functions a type can perform, not to indicate facts about particular types. Instead of marker interfaces, use attributes for this purpose. 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. They 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 such types can introduce.