New Fun Blog – Scott Bilas

Take what you want, and leave the rest (just like your salad bar).

Quickie: Adding Utility Functions To Interfaces

without comments

In working in C#, one thing I miss from C++ is being able to implement interfaces via a rich base class. C#’s lack of multiple inheritance (a good decision on balance) just about prevents this.

I have two things in mind when I say “rich base class”. First is for mixing in object functionality. I’ve spoken about this before. There really is no way to do this nicely in C#. However, today’s post is about the other thing C++ gives you with multiple inheritance: utility methods.

Consider this interface and implementing class, paying attention to the overloads:

[code lang="csharp"]
interface ILogger
{
void Log(string message);
void LogLine(string message);
void Log(string format, params object[] args);
void LogLine(string format, params object[] args);
}

class DebugLogger : ILogger
{
void ILogger.Log(string message)
{ Debug.Write(message); }
void ILogger.LogLine(string message)
{ Debug.WriteLine(message); }
void ILogger.Log(string format, params object[] args)
{ Debug.Write(string.Format(format, args)); }
void ILogger.LogLine(string format, params object[] args)
{ Debug.WriteLine(string.Format(format, args)); }
}
[/code]

The more versions of the log function you want, the more work you have to do in any class implementing the interfaces. You could create a LoggerBase that does all of this, but that is severely limiting.

In C++ it’s easy of course. Make a mixin class.

[code lang="csharp"]
class Logger
{
public:
void Log(const char* message)
{ OnLog(message); }
void LogLine(const char* message)
{ OnLog(message); OnLog("\n"); }

void LogF(const char* format, ...)
{
char buffer[2000];
va_list args;
va_start(args, format);
vsprintf_s(buffer, format, args);
va_end(args);
Log(buffer);
}

void LogLineF(const char* format, ...)
{
char buffer[2000];
va_list args;
va_start(args, format);
vsprintf_s(buffer, format, args);
va_end(args);
LogLine(buffer);
}

protected:
virtual void OnLog(const char* message) = 0;
};

class DebugLogger : public Logger
{
virtual void OnLog(const char* message)
{
OutputDebugString(message);
}
};
[/code]

Exactly one virtual method is required in the derived class. To add new overloads, you just add them to the base, and have them call the virtual. All end up with dynamic behavior. With interfaces in C#, you’re forced to implement each overload. This ends up with a lot of duplication everywhere you implement this same interface. Worse, if you want to add more utility functions to the interface, you break every implementing class, which must now implement that function as well.

The other day, it hit me that there’s an easy and perhaps obvious solution to this in C#: extension methods.

[code lang="csharp"]
interface ILogger
{
void Log(string message);
}

static partial class Extensions
{
public static void LogLine(this ILogger logger, string message)
{
logger.Log(message);
logger.Log("\n");
}

public static void LogFormat(this ILogger logger, string format, params object[] args)
{
logger.Log(string.Format(format, args));
}

public static void LogLineFormat(this ILogger logger, string format, params object[] args)
{
logger.LogFormat(format, args);
logger.Log("\n");
}
}

class DebugLogger : ILogger
{
void ILogger.Log(string message)
{
Debug.Write(message);
}
}
[/code]

This will do what I want. I can implement a minimal interface, and easily add new utility functions. It’s not quite as good as C++, because I can’t store any data in my extension methods, and I must work solely through the published interface, but it’s good enough for 80%.

In one very small way it’s actually better than C++. In C++, that base class isn’t always something that can be changed. Perhaps it was provided by a standard library or a third party. Yet in C#, anybody can create an extension class to add functionality to any other class. So you can add all the overloads you like.

March 21st, 2010 at 11:20 am

Posted in .net,quickie

Leave a Reply