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.
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 IFileCompression
interface 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.
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.
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. The IListable
interface requires a read-only property, ColumnValues
.
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.
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.
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.
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.
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 of ColumnValues
’ explicit implementation.
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).
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.
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
.
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 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, 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.
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.
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.
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.
// ERROR: GetSetting() not available on ISettingsProvider
string ISettingsProvider.GetSetting(
string name, string defaultValue)
{
// ...
}
The results of Listing 7.7 appear in 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.
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.
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.
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
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
.
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
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.
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 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.
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
.
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 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.
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 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.
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.