The joys of new territory in API design


I remember back in the day when we added a first-class notion of properties to the CLR\C#.  Up to that time we just had methods and constructors (events came later still).  We really didn’t know how to use these things (even though they had existed for *years* in other systems).  One of my favorite examples that we actually had checked in as this one from System.Type:


 


public MethodInfo Methods[string methodName, bool ignoreCase] {get;}


 


That is right folks, it was basically modeling a two dimension array of method names and a Boolean… usage was something like:


 


Type t = …


MethodInfo m = t.Method[“IndexOf”, false];


 


How odd is that?  I am sure glad we came to our sense and started to understand what properties are really good for… Just because you technically CAN write code like this doesn’t mean you SHOULD.


 


Well, it is more than a few years later and we have some new territory in API design and we are fighting the same battle.  This time around with generics.  Here is the question I just got over an internal alias:


Is it a good practice to reduce the number of overloaded functions by using a generic type?


I have a case where we have about 8 overloaded functions, and I’m proposing we reduce the number of the overloaded functions by 1/2 if we use a generic type for one of the arguments, and this argument is currently of type “XmlReader” in 4 of the functions, and “JobDefinition” type in the other remaining 4 functions.  So, if we use a generic type in this case, we will be able to pass in “XmlReader” or “JobDefinition” object. And this will reduce the total number of overloaded functions to 4.  So what is your advice?    


 


Basically this person is asking if an usage of generics like this is a good one:


public void Foo<T>(T readerOrJobDef, bool value, string name);


public void Foo<T>(T readerOrJobDef, bool value);


public void Foo<T>(T readerOrJobDef);


 


 


The answer:  No.  this is not a good usage of generics.  Just because you can, doesn’t mean you should… In the case, Anthony Moore does a good job describing what is busted about this plan:


I don’t think generics are right for the scenario you describe, because you can’t constrain them in the right way, and you usually need to constrain generics to do anything interesting with them. While you could potentially reduce your number of overloads by doing this, you would be paying the cost of potentially needing to do late bound invocation or casting, because if the type is not constrained, then it a bit like typing your methods as “Object”. Also, you would open up a number of invalid scenarios when you pass in the wrong type of objects that would have not existed before. From an API consistency point of view, I would not recommend using generics for this.


I could not agree more!  Although reducing API bloat is goodness, generics (or any feature for that matter) should only be used where they are really adding unique value.


 


 

Comments (8)

  1. Mike Scott says:

    >> Up to that time we just had methods and constructors (events came later still). We really didn’t know how to use these things (even though they had existed for *years* in other systems) <<

    That’s odd, Brad. Indeed they had existed for *years* in other systems, such as that one from Borland that Anders and Chuck Jazdzewski wrote. Are you saying he forgot how to use properties and events when he went to Microsoft? :o))

  2. It was not a lack of experience at Microsoft as much as it was a lack of experience on my team… Believe me, Anders had a cow when he saw it.. 😉

  3. Keith Hill says:

    Good call. I have observed the after effects of people using the "new toy" everywhere that it could be shoe-horned in. Later on, folks wake up from the euphoria and it’s hang-over time – and it’s ugly. 🙂

  4. JD says:

    I had meant to comment on that thread….

    Following your own principles is best: write the 3 lines of code the user of the API would have to use:

    With overloading:

    Foo(reader);

    With generics:

    Foo<XmlReader>(reader)

    Having the user write out an ugly generic method invocation is clearly *not* a good tradeoff for reducing the number of overloads by half.

  5. Peter says:

    Hi!

    A question about something else.

    "Platform vs Library Versioning in Longhorn/Orcas"

    http://wesnerm.blogs.com/net_undocumented/2004/06/platform_vs_lib.html

    Looks like serious change… Any comments, Brad?

  6. Brad Abrams says:

    Good question Peter — and what an appropriate thread to ask it on. Although the concepts behind the PL split are years old we are still very nascent in our understanding of how to apply these in the system. We are still actively working out the guidance for WinFX, but I strongly believe we should error on the side of simplicity of model… We have a long road ahead of us in OrcasLonghorn, so I wouldn’t put too much faith in any plans like this one for a while…

    That said, I would love to hear customers reactions to the plan…

  7. Andrey Shchekin says:

    While studying BCL with Reflector, I found an interesting interface, IServiceProvider, that has one method taking Type parameter:

    object GetService(Type serviceType);

    It is obvious that we must perform one additional cast on the result returned by it.

    Is there any way to express this idea in generics ? My question is purely theoretical – I do not ask if it will be done some day. Let’s suppose that I made a similar interface:

    public interface IMyServiceProvider

    {

    T GetService<T>();

    }

    Can I provide a class implementing this interface ? It must implement this method so that it will return null for unsupported types and object of type T for supported.

    So there are two possible solutions – to make a switch on T in the method body (something like switching on gettype(T).FullName), or provide some generic overloads for GetService based on T.

    So the question is simple – what is the easiest and the most appropriate way to perform something based on the generic argument.

    I suppose that there is a very simple answer, but I have some problems with understanding it.

    Sorry for my bad English.