Chapter 7

Interfacing with the Interface

IN THIS CHAPTER

Bullet Going beyond IS_A and HAS_A

Bullet Creating and using interfaces

Bullet Unifying class hierarchies with interfaces

Bullet Managing flexibility via interfaces

A class can contain a reference to another class, which describes the simple HAS_A relationship. One class can extend another class by using inheritance — that's the IS_A relationship. The C# interface implements another, equally important association: the CAN_BE_USED_AS relationship. This chapter introduces C# interfaces and shows some of the numerous ways they increase the power and flexibility of object-oriented programming. Most importantly, you discover how to unify classes with interfaces to produce clearer application code with improved flexibility.

Remember You don’t have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the CSAIO4D2EBK02CH07 folder of the downloadable source. See the Introduction for details on how to find these source files.

Introducing CAN_BE_USED_AS

If you want to jot a note, you can scribble it with a pen, type it into your Personal Digital Assistant (PDA) (a high performance handheld device used for business purposes that comes with a wide variety of add-ons such as bar code readers), or pound it out on your laptop's keyboard. You can fairly say that all three objects — pen, PDA, and computer — implement the TakeANote operation. Suppose that you use the magic of inheritance to implement this concept in C#:

abstract class ThingsThatRecord // The base class
{
abstract public void TakeANote(string note);
}

public class Pen : ThingsThatRecord // A subclass
{
override public void TakeANote(string note)
{
// … scribble a note with a pen …
}
}

public class PDA : ThingsThatRecord // Another subclass
{
override public void TakeANote(string note)
{
// … stroke a note on the PDA …
}
}

public class LapTop : ThingsThatRecord // A third subclass
{
override public void TakeANote(string note)
{
// … tap, tap, tap …
}
}

Tip If the term abstract has you stumped, see Chapter 6 of this minibook and, later in this chapter, read the discussion in the section “Abstract or concrete: When to use an abstract class and when to use an interface.” If the whole concept of inheritance is a mystery, check out Chapter 5 of this minibook. The following simple method shows the inheritance approach working just fine:

void RecordTask(ThingsThatRecord recorder) // Parameter type is base class.
{
// All classes that extend ThingsThatRecord have a TakeANote method.
recorder.TakeANote("Shopping list");
// … and so on.
}

The parameter type is ThingsThatRecord, so you can pass any subclasses to this method, making the method quite general. That might seem like a good solution, but it has two big drawbacks:

  • A fundamental problem: Do Pen, PDA, and LapTop truly have an IS_A relationship? Are those three items all the same type in real life? The issue is that ThingsThatRecord makes a poor base class here.
  • A purely technical problem: You might reasonably derive both LapTop and PDA as subclasses of Computer. But nobody would say that a Pen IS_A Computer. You have to characterize a pen as a type of MechanicalWritingDevice or DeviceThatStainsYourShirt. But a C# class can't inherit from two different base classes at the same time — a C# class can be only one type of item.

So the Pen, PDA, and LapTop classes have in common only the characteristic that they CAN_BE_USED_AS recording devices. Inheritance doesn't apply.

Knowing What an Interface Is

An interface in C# resembles a class with no data members and nothing but abstract methods in older versions of C#, almost like an abstract class — almost:

interface IRecordable
{
void TakeANote(string note);
}

Interfaces created in C# 8.0 and above can have default implementations for their members and also static members for common functionality, making the newer interfaces a little closer to a base class. The interface begins with the interface keyword. It contains nothing but abstract methods. It has no data members and no implemented methods.

Technicalstuff Interfaces can contain a few other features, including properties (covered in Chapter 4 of this minibook), events (covered in Chapter 8 of this minibook), and indexers (covered in Chapter 7 of Book 1). Among the elements that a C# interface cannot exhibit when working with an older implementation are

  • Access modifiers, such as public or private (see Chapter 4 of this minibook)
  • Keywords such as virtual, override, or abstract (see Chapter 6 of this minibook)
  • Data members (see Chapter 1 of this minibook)
  • Implemented methods — nonabstract methods with bodies

When creating an interface using C# 8.0 or above, you can include these additional elements:

  • Constants
  • Operators
  • Static constructors
  • Nested types
  • Static fields, methods, properties, indexers, and events
  • Member declarations using the explicit interface implementation syntax
  • Explicit access modifiers (the default access is public)

Remember There is a distinct separation between interface code created for versions of C# older than 8.0 and interface code created for versions of C# 8.0 and above. It's essential to know which version of C# you’re using when working with interfaces, or you should assume that you need to follow the older rules. Unlike an abstract class, a C# interface isn’t a class. It can’t be subclassed.

How to implement an interface

To put a C# interface to use, you implement it with one or more classes or structures. The class and structure headings might look like this:

// Looks like inheritance, but isn't
class Pen : IRecordable
struct PenDescription : IRecordable

Remember A C# interface specifies that classes or structures which implement the interface must provide specific implementations. For example, any class that implements the IRecordable interface must provide an implementation for the TakeANote method. The method that implements TakeANote doesn't use the override keyword. Using an interface isn’t like overriding a virtual method in classes. Class Pen might look like this:

class Pen : IRecordable
{
public void TakeANote(string note) // Interface method implementations
{ // MUST be declared public.
// … scribble a note with a pen …
}
}

This example fulfills two requirements: Note that the class implements IRecordable and provides a method implementation for TakeANote().

The syntax indicating that a class inherits a base class, such as ThingsThatRecord, is essentially no different from the syntax indicating that the class implements a C# interface such as IRecordable:

public class PDA : ThingsThatRecord …
public class PDA : IRecordable …

Tip Visual Studio can help you implement an interface. Hover the cursor over the interface name in the class heading. A little underline appears underneath the first character of the interface name — it's a Smart Tag. Move the cursor until a menu opens and then choose Implement Interface. Presto! A skeleton framework appears; you fill in the details.

Using the newer C# 8.0 additions

Microsoft added a lot of functionality to C# 8.0 interfaces, so it pays to spend a little more time reviewing them. The IRecordable8 program provides more than the interface code snippets you’ve seen so far so that you can gain an understanding of the C# 8.0 difference. This code requires use of the .NET Core template, rather than the .NET Framework template. In addition, you must choose .NET 6.0 when asked for the Target Framework in the Console Application Wizard. Here is the IRecordable interface used for this example:

public interface IRecordable
{
// This is a default implementation.
public string GetName()
{
return "Writing Device";
}

private static int _numDevices;
public static int NumDevices
{
get { return _numDevices; }
set { if (value >= 0) _numDevices = value; }
}

public static bool IsDeviceAvailable()
{
if (NumDevices > 0)
return true;
else
return false;
}

string Description();

void PerformWrite(string Stuff);
}

This example doesn't show everything you can do, but it provides you with some ideas about what is possible. Notice that the instance method, GetName(), has an implementation, as does the static property, NumDevices, and the static method, IsDeviceAvailable(). You don't have to implement any of these members in your class if the default implementations will work. It’s essential that you create NumDevices as shown because interfaces don’t support the auto-implemented properties that classes do. However, you don’t see an implementation for either Description() or PerformWrite(), so you need to provide an implementation for them in your class. Consequently, this is what a class based on the IRecordable interface might look like:

internal class Pen : IRecordable
{
internal Pen()
{
IRecordable.NumDevices += 1;
}

~Pen()
{
IRecordable.NumDevices -= 1;
}

// Uncomment this method to obtain a specific
// implementation.
//public string GetName()
//{
// return "Pen";
//}

public string Description()
{
return "A device used for writing by hand.";
}

private bool PenWriting
{ get; set; }

public void PerformWrite(string Stuff)
{
if (PenWriting)
{
Console.WriteLine("Pen is writing.");
Console.WriteLine($"The paper contains: {Stuff}.");
}
else
Console.WriteLine("Pen isn't writing.");
}

public void StopWriting()
{
PenWriting = false;
Console.WriteLine("Pen off paper.");
}

public void StartWriting()
{
PenWriting = true;
Console.WriteLine("Pen on paper.");
}
}

The Pen class provides implementations for both Description() and PerformWrite(). In addition, it provides custom properties and methods to make these actions happen. The need to start and stop writing, for example, applies to a pen because your hand must move onto the paper and off the paper as needed. The example has GetName() commented out so that you can see the default implementation in action. If you uncomment this code, you can use the specific implementation for this class instead.

Remember Notice that this example provides both a constructor and a finalizer that update the number of writing devices that are available. This value is part of the interface, so you call the interface to change it. Here is application code that is based on the Pen class.

static void Main(string[] args)
{
Console.WriteLine($"Pen available? {IRecordable.IsDeviceAvailable()}");
IRecordable myPen = new Pen();

Console.WriteLine($"Pen available? {IRecordable.IsDeviceAvailable()}");
((Pen)myPen).StartWriting();
myPen.PerformWrite("Hello There!");
Console.WriteLine($"Using a {myPen.GetName()}.");
((Pen)myPen).StopWriting();
myPen.PerformWrite("Goodbye");
Console.ReadLine();
}

It's possible to call static interface methods from within your code as well as any classes you create. You don’t have to instantiate an object to use them. So, the first check to IRecordable.IsDeviceAvailable() will provide a response of False because the code hasn't created a Pen object, myPen, yet.

If you want to use the interface features with myPen, you must create it as an IRecordable object. This means that you must tell the compiler that myPen is actually a Pen object every time you want to use one of the Pen-specific features, such as StartWriting(), or the compiler will complain. However, if you provide an override for an interface member in your class, the compiler will automatically use the override. Here's the default output from the example:

Pen available? False
Pen available? True
Pen on paper.
Pen is writing.
The paper contains: Hello There!.
Using a Writing Device.
Pen off paper.
Pen isn't writing.

How to name your interface

The .NET naming convention for interfaces precedes the name with the letter I. Interface names are typically adjectives, such as IRecordable.

Why C# includes interfaces

Remember The bottom line with interfaces is that an interface describes a capability, such as Swim Safety Training or Class A Driver’s License. A class implements the IRecordable interface when it contains a full version of the TakeANote method.

More than that, an interface is a contract. If you agree to implement every nondefault member defined in the interface, you get to claim its capability. Not only that, but a client using your class in an application knows calling those methods is possible. Implementing an interface is a promise, enforced by the compiler. (Enforcing promises through the compiler reduces errors.)

Mixing inheritance and interface implementation

Unlike some languages, such as C++, C# doesn't allow multiple inheritance — a class inheriting from two or more base classes. Think of class HouseBoat inheriting from House and Boat. Just don't think of it in C#.

But although a class can inherit from only one base class, it can in addition implement as many interfaces as needed. After defining IRecordable as an interface, a couple of the recording devices looked like this:

public class Pen : IRecordable // Base class is Object.
{
public void TakeANote(string note)
{
// Record the note with a pen.
}
}

public class PDA : ElectronicDevice, IRecordable
{
public void TakeANote(string note)
{
// Record the note with your thumbs or a stylus.
}
}

Class PDA inherits from a base class and implements an interface.

And he-e-e-re's the payoff

To begin to see the usefulness of an interface such as IRecordable, consider this example:

public class Program
{
static public void RecordShoppingList(IRecordable recorder)
{
// Jot it down, using whatever device was passed in.
recorder.TakeANote(…);
}

public static void Main(string[] args)
{
PDA pda = new PDA();
RecordShoppingList(pda); // Oops, battery's low …

Pen pen = new Pen();
RecordShoppingList(pen);
}
}

The IRecordable parameter is an instance of any class that implements the IRecordable interface. RecordShoppingList() makes no assumptions about the exact type of recording object. Whether the device is a PDA or a type of ElectronicDevice isn't important, as long as the device can record a note.

That concept is immensely powerful because it lets the RecordShoppingList() method be highly general — and thus possibly reusable in other programs. The method is even more general than using a base class such as ElectronicDevice for the argument type, because the interface lets you pass almost arbitrary objects that don't necessarily have anything in common other than implementing the interface. They don’t even have to come from the same class hierarchy, which truly simplifies the designing of hierarchies, for example.

Technicalstuff Some programmers and many older online articles use the term interface in more than one way. You can see the C# keyword interface and how it’s used. People also talk about a class’s public interface, or the public methods and properties that it exposes to the outside world. Most sources now use the term Application Programming Interface or API to specify the methods, events, and properties used to access a class. This book uses API to refer to this public part of a class.

Using an Interface

In addition to your being able to use a C# interface for a parameter type, an interface is useful as

  • A method return type
  • The base type of a highly general array or collection
  • A more general kind of object reference for variable types

The previous section explains the advantage of using a C# interface as a method parameter. The following sections tell you about other interface uses.

As a method return type

You farm out the task of creating key objects you need to a factory method. Suppose that you have a variable like this one:

IRecordable recorder = null; // Yes, you can have interface-type variables.

Somewhere, maybe in a constructor, you call a factory method to deliver a particular kind of IRecordable object:

recorder = MyClass.CreateRecorder("Pen"); // A factory method is often static.

where CreateRecorder() is a method, often on the same class, that returns not a reference to a Pen but, rather, an IRecordable reference:

static IRecordable CreateRecorder(string recorderType)
{
if (recorderType == "Pen") return new Pen();

}

You can find more about the factory idea in the section “Hiding Behind an Interface,” later in this chapter. But note that the return type for CreateRecorder() is an interface type.

As the base type of an array or collection

Suppose that you have two classes, Animal and Robot, and that both are abstract. You want to set up an array to hold both thisCat (an Animal) and thatRobot (a Robot). The only way is to fall back on type Object, the ultimate base class in C# and the only base class that's common to both Animal and Robot as well as to their subclasses:

object[] things = new object[] { thisCat, thatRobot };

That's poor design for lots of reasons. But suppose that you’re focused on the objects’ movements. You can have each class implement an IMovable interface:

interface IMovable
{
void Move(int direction, int speed, int distance);
}

and then set up an array of IMovables to manipulate your otherwise incompatible objects:

IMovable[] movables = { thisCat, thatRobot };

The interface gives you a commonality that you can exploit in collections.

As a more general type of object reference

The following variable declaration refers to a specific, physical, concrete object (see the later section “Abstract or concrete: When to use an abstract class and when to use an interface”):

Cat thisCat = new Cat();

One alternative is to use a C# interface for the reference:

IMovable thisMovableCat = (IMovable)new Cat(); // Note the required cast.

Now you can put any object into the variable that implements IMovable. This practice has wide, powerful uses in object-oriented programming, as you can see in the next section.

Using the C# Predefined Interface Types

Because interfaces are extremely useful, you find a considerable number of interfaces in the .NET class library. Among the dozen or more interfaces in the System namespace alone are IComparable, IComparable<T>, IDisposable, and IFormattable. The System.Collections.Generics namespace (https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic) includes IEnumerable<T>, IList<T>, ICollection<T>, and IDictionary<TKey, TValue>. And there are many more. Those with the <T> notation are generic interfaces. Book 1, Chapter 6 explains the <T> notation in the discussion of collection classes.

Two interfaces that are commonly used are IComparable and IEnumerable — largely superseded now by their generic versions IComparable<T> (read as “IComparable of T”) and IEnumerable<T>.

The “Implementing the incomparable IComparable<T> interface” section, later in this chapter, shows you the IComparable<T> interface. This interface lets you compare all sorts of objects, such as Students, to each other, and enables the Sort() method that all arrays and most collections supply. IEnumerable<T> makes the powerful foreach loop work. Most collections implement IEnumerable<T>, so you can iterate the collections with foreach. You can find an additional major use for IEnumerable<T>, as the basis for query expressions, described in Book 1.

Looking at a Program That CAN_BE_USED_AS an Example

Interfaces help you perform tasks in ways that classes can't because you can implement as many interfaces as you want, but you can inherit only a single class. The SortInterface program that appears in the following sections demonstrates the use of multiple interfaces and how you can use multiple interfaces effectively. To fully understand the impact of working with multiple interfaces, it’s important to break the SortInterface program into sections to demonstrate various principles.

Creating your own interface at home in your spare time

The following IDisplayable interface is satisfied by any class that contains a Display() method (and declares that it implements IDisplayable, of course). Display() returns a string representation of the object that can be displayed using WriteLine().

// IDisplayable -– Any object that implements the Display() method
interface IDisplayable
{
// Return a description of yourself.
string Display();
}

The following Student class implements IDisplayable:

class Student : IDisplayable
{
public Student(string name, double grade)
{ Name = name; Grade = grade; }
public string Name { get; private set; }
public double Grade { get; private set; }

public string Display()
{
string padName = Name.PadRight(9);
return $"{padName}: {Grade:N0}";
}
}

Display() uses string interpolation (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) to format the information. It relies on a numeric string formatter for the Grade information (https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings).

The following DisplayArray() method in the Program class (place it before Main()) takes an array of any objects that implement the IDisplayable interface. Each of those objects is guaranteed (by the interface) to have its own Display() method.

// DisplayArray -– Display an array of objects that implement
// the IDisplayable interface.
public static void DisplayArray(IDisplayable[] displayables)
{
foreach (IDisplayable disp in displayables)
Console.WriteLine($"{disp.Display()}");
}

Implementing the incomparable IComparable<T> interface

C# defines the interface IComparable<T> this way (don't add this code to the SortInterface example):

interface IComparable<T>
{
// Compare the current T object to the object 'item'; return a
// 1 if larger, -1 if smaller, and 0 if the same.
int CompareTo(T item);
}

A class implements the IComparable<T> interface by implementing a CompareTo() method. Notice that CompareTo() takes an argument of type T, a type you supply when you instantiate the interface for a particular data type, as in this example:

class SoAndSo : IComparable<SoAndSo> // Make me comparable.

When you implement IComparable<T> for your class, its CompareTo() method should return 0 if the two items (of your class type) being compared are “equal” in a way that you define. If not, it should return 1 or –1, depending on which object is “greater.”

It seems a little Darwinian, but you could say that one Student object is “greater than” another Student object if the subject student's grade-point average is higher. Implementing the CompareTo() method implies that the objects have a sorting order. If one student is greater than another, you must be able to sort the students from least to greatest. In fact, most collection classes (including arrays but not dictionaries) supply a Sort() method something like this:

void Sort(IComparable<T>[] objects);

This method sorts a collection of objects that implement the IComparable<T> interface. It doesn't even matter which class the objects belong to. For example, they could even be Student objects. Collection classes such as arrays or List<T> could even sort this updated version of the Student class (with changes shown in bold):

// Student -- Description of a student with name and grade
class Student : IComparable<Student>, IDisplayable // Instantiation
{
public Student(string name, double grade)
{ Name = name; Grade = grade; }
public string Name { get; private set; }
public double Grade { get; private set; }

public string Display()
{
string padName = Name.PadRight(9);
return $"{padName}: {Grade:N0}";
}

// Implement the IComparable<T> interface:
// CompareTo -- Compare another object (in this case, Student objects)
// and decide which one comes after the other in the sorted array.
public int CompareTo(Student rightStudent)
{
// Compare the current Student (call her 'left') against the other
// student (call her 'right').
Student leftStudent = this;

// Generate a -1, 0 or 1 based on the Sort criteria (the student's
// grade). You could use class Double's CompareTo() method instead.
if (rightStudent.Grade < leftStudent.Grade)
{
Console.WriteLine($"{rightStudent.Name} < {leftStudent.Name}");
return -1;
}
if (rightStudent.Grade > leftStudent.Grade)
{
Console.WriteLine($"{rightStudent.Name} > {leftStudent.Name}");
return 1;
}

Console.WriteLine($"{rightStudent.Name} = {leftStudent.Name}");
return 0;
}
}

Sorting an array of Students is reduced to this single call:

// Where Student implements IComparable<T>
void MyMethod(Student[] students)
{
Array.Sort(students); // Sort array of IComparable<Student>s
}

You provide the comparator (CompareTo()), and Array does all the work.

Creating a list of students

To test everything you've written so far, you need a list of students. The example provides a simple mechanism for doing so in a method called CreateStudentList() that relies on two fixed lists: names and grades. You place it at the end of the Student class. Here's the code you need:

// CreateStudentList - To save space here, just create
// a fixed list of students.
static string[] names = { "Homer", "Marge", "Bart", "Lisa", "Maggie" };
static double[] grades = { 0, 85, 50, 100, 30 };

public static Student[] CreateStudentList()
{
Student[] students = new Student[names.Length];
for (int i = 0; i < names.Length; i++)
{
students[i] = new Student(names[i], grades[i]);
}
return students;
}

As you can see, CreateStudentList() defines an array of students that has the same length as the names list and relies on the Student class constructor. For each name and grade pair, the code creates another entry in students and then returns the completed array to the caller.

Testing everything using Main()

At this point, you have everything needed to sort a list of students. All you need is some code in Main() to demonstrate the functionality. The following code tests your Student class:

static void Main(string[] args)
{
// Sort students by grade…
Console.WriteLine("Using Array.Sort() to sort.");

// Get an unsorted array of students.
Student[] students = Student.CreateStudentList();

// Use the IComparable interface to sort the array.
Array.Sort(students);

// Now the IDisplayable interface to display the results.
DisplayArray(students);

// Use Language Integrated Query (LINQ) instead.
Console.WriteLine(" Using LINQ to sort.");
Student[] students2 = Student.CreateStudentList();
students2 = students2.OrderBy(C => C).ToArray();
DisplayArray(students2);

Console.Read();
}

Main() begins by creating a student list. It then sorts the students using Array.Sort(), which actually calls the CompareTo() method in Student. The code then calls DisplayArray() so that you can see the sorted output.

For comparison purposes, this example also uses Language Integrated Query (LINQ) to sort the values. To use LINQ, you must add the using System.Linq; statement to the beginning of your code file. Again, this method relies on CompareTo(), but the process is different. You can read more about LINQ at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/. For now, just know that it's a different type of sorting technique. Here’s what you see when you run this application:

Using Array.Sort() to sort.
Homer < Marge
Homer < Bart
Marge > Bart
Homer < Lisa
Bart < Lisa
Marge < Lisa
Homer < Maggie
Bart > Maggie
Lisa : 100
Marge : 85
Bart : 50
Maggie : 30
Homer : 0

Using LINQ to sort.
Homer < Bart
Maggie < Bart
Lisa > Bart
Marge > Bart
Bart = Bart
Bart = Bart
Lisa = Lisa
Marge < Lisa
Lisa = Lisa
Homer = Homer
Maggie > Homer
Lisa : 100
Marge : 85
Bart : 50
Maggie : 30
Homer : 0

The output statements show the action of Array.Sort() when combined with CompareTo() for this particular class. You also see that LINQ requires more steps to get the job done, but produces the same result as Array.Sort(). The goal is to sort the list as quickly as possible.

Unifying Class Hierarchies

Figure 7-1 shows the Robot and Animal hierarchies. Some, but not all, of the classes in each hierarchy not only inherit from the base classes, Robot or Animal, but also implement the IPet interface (not all animals are pets).

Schematic illustration of a tale of two class hierarchies and one interface.

FIGURE 7-1: A tale of two class hierarchies and one interface.

The following code snippet shows how to implement the hierarchy. Note the properties in IPet. This code shows how you specify properties in interfaces using the approach that works for all versions of C# (the “Using the newer C# 8.0 additions” section of the chapter shows the newer C# 8.0 and above additions). If you need both getter and setter, just add set; after get;.

// Two abstract base classes and one interface
abstract class Animal
{
abstract public void Eat(string food);
abstract public void Sleep(int hours);
abstract public int NumberOfLegs { get; }
public void Breathe() { … } // Nonabstract, implementation not shown.
}

abstract class Robot
{
public virtual void Speak(string whatToSay) { … } // Impl not shown
abstract public void LiftObject(object o);
abstract public int NumberOfLegs { get; }
}

interface IPet
{
void AskForStrokes();
void DoTricks();
int NumberOfLegs { get; } // Properties in interfaces look like this.
string Name { get; set; } // get/set must be public in implementations.
}

// Cat -- This concrete class inherits (and partially implements)
// class Animal and also implements interface IPet.
class Cat : Animal, IPet
{
public Cat(string name) { Name = name; }

// 1. Overrides and implements Animal members (not shown).
// 2. Provides additional implementation for IPet.
// Inherits NumberOfLegs property from base class, thus meeting
// IPet's requirement for a NumberOfLegs property.
#region IPet Members
public void AskForStrokes() …
public void DoTricks() …
public string Name { get; set; }
#endregion IPet Members

public override string ToString() { return Name; }
}

class Cobra : Animal
{
// 1. Inherits or overrides all Animal methods only (not shown).
}

class Robozilla : Robot // Not IPet
{
// 1. Override Speak.
public override void Speak(string whatToSay)
{ Console.WriteLine("DESTROY ALL HUMANS!"); }

// 2. Implement LiftObject and NumberOfLegs, not all shown.
public override void LiftObject(object o) …
public override int NumberOfLegs { get { return 2; } }
}

class RoboCat : Robot, IPet
{
public RoboCat(string name) { Name = name; }

// 1. Override some Robot members, not all shown:
#region IPet Members
public void AskForStrokes() …
public void DoTricks() …
public string Name { get; set; }
#endregion IPet Members
}

The code shows two concrete classes that inherit from Animal and two that inherit from Robot. However, you can see that neither class Cobra nor class Robozilla implements IPet — probably for good reasons. Most people don't plan to watch TV with a pet cobra beside them on the couch, and a robozilla sounds nasty, too. Some of the classes in both hierarchies exhibit what you might call “petness” and some don’t.

The point of this section is that any class can implement an interface, as long as it provides the right methods and properties. RoboCat can carry out the AskForStrokes() and DoTricks() actions and have the NumberOfLegs property, as can Cat in the Animal hierarchy — all while other classes in the same hierarchies don't implement IPet.

Hiding Behind an Interface

Often this book discusses code that (a) you write but (b) someone else (a client) uses in a program (you may be the client yourself, of course). Sometimes you have a complex or tricky class whose entire API you would truly rather not expose to clients. For various reasons, however, it includes some dangerous operations that nonetheless have to be public. (For example, you might not want to expose code for deleting records, but the client may really need that functionality unless you want to create a support group devoted to deleting records.) Ideally, you would expose a safe subset of your class’s public methods and properties and hide the dangerous ones. C# interfaces can do that, too.

Here’s a different Robozilla class, with several methods and properties that amateurs can use safely and enjoyably. But Robozilla also has some advanced features that can be, well, scary:

public class Robozilla // Doesn't implement IPet!
{
public void ClimbStairs(); // Safe
public void PetTheRobodog(); // Safe? Might break it.
public void Charge(); // Maybe not safe
public void SearchAndDestroy(); // Dangerous
public void LaunchGlobalThermonuclearWar(); // Catastrophic
}

Remember You want to expose only the two safer methods while hiding the last three dangerous ones. Here’s how you can do that by using a C# interface:

  1. Design a C# interface that exposes only the safe methods:

    public interface IRobozillaSafe
    {
    void ClimbStairs();
    void PetTheRobodog();
    }

  2. Modify the Robozilla class to implement the interface. Because it already has implementations for the required methods, all you need is the IRobozillaSafe notation on the class heading:

    public class Robozilla : IRobozillaSafe …

  3. Give your clients a way to instantiate a new Robozilla, but return to them a reference to the interface. Now you can just keep Robozilla itself a secret from everybody and give most users the IRobozillaSafe interface. This example uses a static factory method added to class Robozilla:

    // Create a Robozilla but return only an interface reference.
    public static IRobozillaSafe CreateRobozilla(<parameter list>)
    {
    return (IRobozillaSafe)new Robozilla(<parameter list>);
    }

  4. Clients then use Robozilla like this:

    IRobozillaSafe myZilla = Robozilla.CreateRobozilla(…);
    myZilla.ClimbStairs();
    myZilla.PetTheRobodog();

It's that simple. Using the interface, they can call the Robozilla methods that it specifies — but not any other Robozilla methods. However, expert programmers can defeat this ploy with a simple cast:

Robozilla myKillaZilla = (Robozilla)myZilla;

Doing so is usually a bad idea, though. The interface has a purpose: to keep bugs at bay. In real life, programmers sometimes use this hand-out-an-interface technique with the complex DataSet class used in ADO.NET (https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-overview) to interact with databases. A DataSet can return a set of database tables loaded with records — such as a table of Customers and a table of Orders. (Modern relational databases, such as Oracle and SQL Server, contain tables linked by various relationships. Each table contains lots of records, where each record might be, for example, the name, rank, and serial number of a Customer.)

Unfortunately, if you hand a client a DataSet reference (even through a read-only property's get clause), the client can muddle the situation by reaching into the DataSet and modifying elements that you don't want modified. To prevent such mischief you can:

  • Return a DataView object, which is read-only.
  • Create a C# interface to expose a safe subset of the operations available on the DataSet. Then you can subclass DataSet and have the subclass (call it MyDataSet) implement the interface.
  • Give clients a way to obtain an interface reference to a live MyDataSet object and let them have at it in relative safety — through the interface.

Tip You usually shouldn't return a reference to a collection, either, because it lets anyone alter the collection outside the class that created it. Remember that the reference you hand out can still point to the original collection inside your class. That’s why List<T>, for instance, provides an AsReadOnly() method. This method returns a collection that can't be altered:

private List<string> _readWriteNames = … // A modifiable data member

ReadonlyCollection<string> readonlyNames = _readWriteNames.AsReadOnly();
return readonlyNames; // Safer to return this than _readWriteNames.

Although it doesn’t qualify as using an interface, the purpose is the same.

Inheriting an Interface

A C# interface can inherit the methods of another interface. However, interface inheritance may not always be true inheritance, no matter how it may appear. The following interface code lists a base interface, much like a base class, in its heading:

interface IRobozillaSafe : IPet // Base interface
{
// Methods not shown here …
}

By having IRobozillaSafe “inherit” IPet, you can let this subset of Robozilla implement its own petness without trying to impose petness inappropriately on all of Robozilla:

class PetRobo : Robozilla, IRobozillaSafe // (also an IPet by inheritance)
{
// Implement Robozilla operations.
// Implement IRobozillaSafe operations, then …
// Implement IPet operations too (required by the inherited IPet interface).
}

// Hand out only a safe reference, not one to PetRobo itself.
IPet myPetRobo = (IPet)new PetRobo();
// … now call IPet methods on the object.

The IRobozillaSafe interface inherits from IPet. Classes that implement IRobozillaSafe must therefore also implement IPet to make their implementation of IRobozillaSafe complete. This type of inheritance isn't the same concept as class inheritance. For instance, class PetRobo in the previous example can have a constructor, but no equivalent of a base-class constructor exists for IRobozillaSafe or IPet. Older interfaces don't have constructors (starting with C# 8.0, interfaces can have a static constructor as described at https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors#static-constructors). More important, polymorphism doesn’t work the same way with interfaces. Though you can call a method of a subclass through a reference to the base class (class polymorphism), the parallel operation involving interfaces (interface polymorphism) doesn’t work: You can’t call a method of the derived interface (IRobozillaSafe) through a base interface reference (IPet).

Although interface inheritance isn't polymorphic in the same way that class inheritance is, you can pass an object of a derived interface type (IRobozillasafe) through a parameter of its base interface type (IPet). Therefore, you can also put IRobozillasafe objects into a collection of IPet objects.

Using Interfaces to Manage Change in Object-Oriented Programs

Interfaces are the key to object-oriented programs that bend flexibly with the winds of change. Your code will laugh in the face of new requirements.

Remember You've no doubt heard it said, “Change is a constant.” When you hand a new program to a bunch of users, they soon start requesting changes. Add this feature, please. Fix that problem, please. (Although users aren’t usually so polite; they’ll likely snarl at you and say something like, “Who in the world implements a program without this feature?”) The RoboWarrior has feature X, so why doesn’t Robozilla? Many programs have a long shelf life. Thousands of programs, especially old Fortran and Cobol programs, have been in service for 20 or 30 years, or longer. They undergo lots of maintenance in that extended time span, which makes planning and designing for change one of your highest priorities.

Here’s an example: In the Robot class hierarchy, suppose that all robots can move in one way or another. Robocats saunter. Robozillas charge — at least when operated by a power (hungry) user. And Robosnakes slither. One way to implement these different modes of travel involves inheritance: Give the base class, Robot, an abstract Move() method. Then each subclass overrides the Move() method to implement it differently:

abstract public class Robot
{
abstract public void Move(int direction, int speed);
// …
}

public class Robosnake : Robot
{
public override void Move(int direction, int speed)
{
// A real Move() implementation here: slithering.
… some real code that computes angles and changes
snake's location relative to a coordinate system, say …
}
}

But suppose that you often receive requests to add new types of movement to existing Robot subclasses. “Please make Robosnake undulate rather than slither,” maybe. Now you have to open up the Robosnake class and modify its Move() method directly.

Remember After the Move() method is working correctly for slithering, most programmers would prefer not to meddle with it. Implementing slithering is difficult, and changing the implementation can introduce brand-new bugs. If it ain't broke, don’t fix it.

Making flexible dependencies through interfaces

There must be a way to implement Move() that doesn’t require you to open a can of worms every time a client wants wriggling instead. You can use interfaces, of course! Look at the following code that uses HAS_A, a now-familiar relationship between two classes in which one class contains the other:

public class Robot
{
// This object is used to implement motion.
protected Motor _motor = new Motor(); // Refers to Motor by name
// …
}

internal class Motor { … }

The point about this example is that the contained object is of type Motor, where Motor is a concrete object. (That is, it represents a real item, not an abstraction.) HAS_A sets up a dependency between classes Robot and Motor: Robot depends on the concrete class Motor. A class with concrete dependencies is tightly coupled to them: When you need to replace Motor with something else, code that depends directly on Motor like this has to change, too. Instead, insulate your code by relying only on the API of dependencies, which you can do with interfaces.

Abstract or concrete: When to use an abstract class and when to use an interface

In Chapter 6 of this minibook, the discourse about birds says, “Every bird out there is a subtype of Bird.” In other words, a duck is an instance of a subclass Duck. You never see an instance of Bird itself — Bird is an abstraction. Instead, you always see concrete, physical ducks, sparrows, or hummingbirds. Abstractions are concepts. As living creatures, ducks are real, concrete objects. Also, concrete objects are instances of concrete classes. (A concrete class is a class that you can instantiate. It lacks the abstract keyword, and it implements all methods.)

Remember You can represent abstractions in two ways in C#: with abstract classes or with C# interfaces. The two have differences that can affect your choice of which one to use:

  • Use an abstract class when you can profitably share an implementation with subclasses — the abstract base class can contribute real code that its subclasses can use by inheritance. For instance, maybe class Robot can handle part of the robot's tasks, just not movement.

    An abstract class doesn’t have to be completely abstract. Though it has to have at least one abstract, unimplemented method or property, some can provide implementations (bodies). Using an abstract class to provide an implementation for its subclasses to inherit prevents duplication of code. That’s always a good thing.

  • Use an interface when you can’t share any implementation for the most part or your implementing class already has a base class.

    Most C# interfaces are purely, totally abstract. An older C# interface supplies no implementation of any of its methods (C# 8.0 and above allows a default implementation, which you can override or not, as needed). Yet it can also add flexibility that isn’t otherwise possible. The abstract class option may not be available because you want to add a capability to a class that already has a base class (that you can’t modify). For example, class Robot may already have a base class in a library that you didn’t write and therefore can’t alter. Interfaces are especially helpful for representing completely abstract capabilities, such as movability or displayability, that you want to add to multiple classes that may otherwise have nothing in common — for example, being in the same class hierarchy.

Doing HAS_A with interfaces

You discovered earlier in this chapter that you can use interfaces as a more general reference type. The containing class can refer to the contained class not with a reference to a concrete class but, rather, with a reference to an abstraction. Either an abstract class or a C# interface will work:

AbstractDependentClass dependency1 = …;
ISomeInterface dependency2 = …;

Suppose that you have an IPropulsion interface:

interface IPropulsion
{
void Movement(int direction, int speed);
}

Class Robot can contain a data member of type IPropulsion instead of the concrete type Motor:

public class Robot
{
private IPropulsion _propel; // <--Notice the interface type here.

// Somehow, you supply a concrete propulsion object at runtime …
// Other stuff and then:
public void Move(int speed, int direction)
{
// Use whatever concrete propulsion device is installed in _propel.
_propel.Movement(speed, direction); // Delegate to its methods.
}
}

Robot's Move() method delegates the real work to the object referred to through the interface. Be sure to provide a way to install a concrete Motor or Engine or another implementer of IPropulsion in the data member. Programmers often install that concrete object — “inject the dependency” — by passing it to a constructor:

Robot r = new Robosnake(someConcreteMotor); // Type IPropulsion

or by assigning it via a setter property:

r.PropulsionDevice = someConcreteMotor; // Invokes the set clause

Another approach to dependency injection is to use a factory method (discussed in the “As a method return type” section, earlier in this chapter, and illustrated in the section “Hiding Behind an Interface”):

IPropulsion _propel = CreatePropulsion(); // A factory method

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

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