We ran into a rather difficult class design problem recently that reveals a shortcoming in C# and, apparently, the .NET runtime (specifically, the Common Language Infrastructure, or CLI). It’s a pretty common problem, and I’m a little bit surprised it hasn’t been addressed.
As you know, C# doesn’t allow multiple inheritance. It does, however, allow classes to implement interfaces, which is kind of like inheriting behaviors. The big difference is that with interfaces you have to supply the implementation in your class. With inheritance, you get a default implementation along with the interface.
And before you flame me, please understand that I’m well aware of the many differences between multiple inheritance and implementing interfaces, including the many assumptions that come along with multiple inheritance. The fact remains, though, that the general behavior of a class that implements an interface will be very similar to the behavior of a class that inherits the behaviors defined by that interface. The implementations are quite different, of course, and inheritance carries with it some often unacceptable baggage, but from the outside looking in, things look very much the same.
Interfaces are very handy things, but they can get unwieldy. Consider, for example, an interface called ITextOutput that contains 4 methods:
interface ITextOutput
{
void Write(string s);
void Write(string fmt, params object[] parms);
void WriteLine(string s);
void WriteLine(string fmt, params object[] parms);
}
The idea here is that you want to give classes the ability to output text strings. If a class implements ITextOutput, then clients can call the Write or WriteLine methods, and the supplied string will go to the object’s output device. So, a class called Foo might implement the interface:
class Foo : ITextOutput
{
public void Write(string s)
{
Console.WriteLine(s);
}
public void Write(string fmt, params object[] parms)
{
Write(string.Format(fmt, parms));
}
public void WriteLine(string s)
{
Write(s + Environment.NewLine);
}
public void WriteLine(string fmt, params object[] parms)
{
WriteLine(string.Format(fmt, parms));
}
}
Easy enough, right? Except that every class that implements ITextOutput has to implement all four methods. If you look closely, you’ll see that the last three methods all end up calling the first after formatting their output. In most cases, the only method that will change across different classes that implement this interface will be the first Write method. You might want to change the output device, for example, or include a date and time stamp on the output line.
C# does not provide a good solution to this problem. As I see it, you have the following options:
- Implement the methods as shown in each class that implements ITextOutput. This is going to be tedious and fraught with error. Somebody is going to make a mistake in all that boilerplate code and the resulting bug will appear at the worst possible time—quite possibly after the product has shipped.
- Structure your class hierarchy so that every object inherits from a common TextOutputter class that implements the interface. This is a very good solution if you can do it. For those classes that inherit from some other base class, you can implement the interface as shown above. I have difficulty with this solution because it’s saying, in effect, “Foo IS-A TextOutputter that also does other stuff.” In reality what we really want to say is, “Foo IS-A object (or some other base class) that implements the ITextOutput functionality. It might sound like a fine distinction, but it matters. A lot. Especially when refactoring code.
- Forget about inheritance and interfaces and make Foo contain a member that implements the interface. Something like:
class Foo { public ITextOutput Outputter = new TextOutputter(); }
This will work, but it’s annoying to clients. Rather than calling Foo.Write, for example, they have to call Foo.Outputter.Write. And class designers are free to change the name of the Outputter member to anything. The result is that clients can’t tell by looking at the class declaration if it implements the ITextOutput interface. Instead, they have to go looking for a member variable (or property) that implements it.
Any way you look at it, it’s messy. As a client, I’d expect class designers to bite the bullet and go with the first option, and test thoroughly. In truth, I think that’s the only reasonable option, painful as it is. As a designer, I’d grumble about the need for all that extra typing, but I’d do it. I’d be embarrassed to release a class that implemented either of the other solutions because as a user I’d be annoyed by either of the other implementations. But I sure wish there were another way to do it.
Delphi solved this problem by using what’s called implementation by delegation. The technique involves creating a member that implements the interface (similar to the third option shown above), and delegating calls to interface methods to that object. In C#, if such a feature existed, the syntax might look something like this:
class Foo: ITextOutput
{
public ITextOutput Outputter =
new TextOutputter() implements ITextOutput;
}
Clients could then call the Write method on an object of type Foo, just as they would with the first option. The runtime (or the compiler, maybe) would then delegate such interface calls to the Outputter member. We have the best of both worlds: real interfaces, and we don’t have to repeatedly type all that boilerplate code.
I’m not the first one to run into this problem or to suggest the solution. Steve Teixeira mentioned it in his blog over three years ago, and linked to this blog entry from 2003. Steve, by the way, is the one who came up with the idea for Delphi. He says that it’s not currently possible to do such a thing in .NET because it can’t be made verifiably type-safe. I don’t understand why not, but I’ll defer to his judgement here.
This type of thing is trivial to implement in languages that support multiple inheritance. But I don’t think I’m willing to accept the problems with multiple inheritance in order to get this one benefit. It’s a moot point anyway, as it’s quite unlikely that .NET will support multiple inheritance any time soon.
I’d sure be interested to find out how others handle this situation in C# or other .NET languages. Drop me a line and let me know.