IDispatchEx2 here we come... My comments on Brad's dilemma

Brad Abrams, who blogs most excellently about API design stuff  posted today about the whole pickle with COM naming conventions and how it impacts .NET, notably the problem that an evolving object model eventually leads to “grafts” being put on the API.  For example, imagine that first there was IDispatch, then comes IDispatchEx, then comes IDispatchEx2.

The nub of Brad's question comes from the fact that as he was reviewing some of the Whidbey interfaces, he came across IDataReader2.  The question, would we as developers prefer IDataReader2 or IDataReaderFoo, which implies the foo-ness of the extension.

One of the great things that .NET allowed Microsoft to do was more or less start from scratch.  CreateWindowEx?  ExitWindowsEx?  All gone!  (There's plenty more, but my Win32 skills have largely fallen by the wayside.  Anyway, what's the Ex?  Extended?  Extension?  Extra?  One for Raymond Chen...)  With .NET, Microsoft got to start again, which means apart from weirdness like everything's supposed to have a strong Pascal name and some things don't (consider XmlDocument, but System.IO - should be System.Io?), everything looks clean and well designed.  But, as .NET evolves, what happens - IDataReader2, IDataReader27ExEx, and so on.

I think with Win32, the Ex extension worked because you were just talking about one function - CreateWindowEx is CreateWindow with extra bits.  COM gets more tricky.  IDispatchEx, however, has tons more features that IDispatch.  If you check the MSDN blurb about it, you'll find about a dozen bullets describing just what's so Ex about IDispatchEx.  So to me, Ex on an interface is a pain in the butt because I have to remember that if I have an IDispatch, the extra features that I want might be on another interface.  So, in COM, I QueryInterface for IDispatchEx in order to get at my objects.

I'll freely admit that types in .NET are going to end up getting extended and enhanced (read “evolved”) over time, but I really must prefer the foo-ness method.  I would go so far as to say that IDataReader2 is crazyness - all that's telling me is that it's a later version of the interface!  (Of course, so does IDataReaderExExEx, but then we're getting unreadable.) 

The idea of implying behavior to an interface makes sense, if we're saying that it's common for a class that implements an interface to support a whole load more behaviour.  For IDispatchEx, a lot of those new features seem to be around DISPIDs, so how would IDispatchDispIdBehavior be? 

What's not so clear to me is what happens when a method changes, or you want to add an overload to an existing method.  Consider an interface like this.

interface IFoo
{
    Type FindType(string name);
}

You find that people consuming this interface are always writing code like this:

Type foundType = myFoo.FindType(”Hello.World”);
if(foundType == null)
    throw new blahblah...

So, you think to yourself, I'll add an overload, so I want IFoo to look like this:

interface IFoo
{
    Type FindType(string name);
    Type FindType(string name, bool throwIfNotFound);
}

...the second method here throwing an exception if the type was not found.

Brad's problem (which I suppose is all our problem) here is that you can't change IFoo without it being a breaking change, so you can't do that.  But you can do this:

interface IFoo
{
    Type FindType(string name);
}

interface IFoo2 : IFoo
{
    Type FindType(string name, bool throwIfNotFound);
}

...the developer can check myFoo to see if it is IFoo2 and then call the method with the built in error checking.  If not, he/she has to do the checking him/herself.

For the life of me, I can't think of a neat way of doing this.  In this case IFoo2 is *better* than the original interface - it really is a 2.0 interface - they've looked at it, and they've made a major change.

One way around this is if the number at the end matched the framework version number.  For example e.g. IDataReader2 for .NET 2.0, IDataReader11 for .NET 1.1 (not sure how to separate them without putting 'p' or an evil underscore in the middle - and I guess we'll have thrown away .NET by the time 10.0 rolls around).  I'm sure that they could (and will) put the ObsoleteAttribute on the interface, so that if I compile anything against the old version it'll throw a warning. 

To me, if foo-ness makes sense, if classes implementing an interface suddenly start all having the same behaviours (like IDispatch-implementing classes tendency to support DISPID queries), then great, lets have IDataReaderFoo.  I know that I need foo-ish stuff, so I can query the object for the interface that defines the foo-ish contract.

If foo-ness doesn't make sense because we're just improving the stuff that's already there, then let's hack the version number of the Framework onto the end of the interface name (or even the base class I guess) against the Framework version number. 

If I build an app in 2.0 and move to 2.1, the ObsoleteAttribute should be able to tell me that where I've implemented IDataReader2 (the 2.0 version), IDataReader21 (the 2.1 version) is now available and I should use that again.  I can then Google for the interface name and hopefully hit some blogs/cool commuity sites/MSDN telling me what new features are in that interface and why I might want to use it.  (Some pragma to be able to supress that warning might be nice?)

I know that the naming is ugly, but I really don't want to have to remember in five years time on .NET 3.0 that I'm supposed to be using IDataReader4 and IDataTableExExEx.  I want to know just by looking at the name whether it matches the BCL version that I'm using, and I want to be able to find out easily why I should be using, the compiler should tell me that I might have forgotten something, and (haha), I don't want my code to break.

Then again, we could throw away the whole concept of interfaces and work out some replacement that doesn't require this sort of hack.  :-)

1 Comment

  • Now, this I don't like. In my opinion, an interface should be named in a way that reflects its use, not its version. I also think it would get confusing when it goes IDataReader21, then IDataReader4, because no major changes happened during the 3.0 version.

    A cleaner solution may be an attribute that can directly apply a version to the interface\class and assembly level attribute that determines a range of valid versions. I was actually thinking about this last night, as I have a piece of code that could benifit from metadata level information refering to the version of the object instead of the assembly.

    But, there are lots of problems with that too, ;).

Comments have been disabled for this content.