The trick with params

I am doing some WinFX API reviews tonight and I ran across this pattern in a number of places:


/*1*/ public virtual void AddNamedParameter(string parameterName);

/*2*/ public virtual void AddNamedParameter(string parameterName, object parameterValue);

/*3*/ public virtual void AddNamedParameter(string parameterName, params object[] parameterValues);


Now I have not talked to the team yet, but my hunch tells me these guys are overdoing the params thing.    I suspect they want to enable callers of the form:

p.AddNamedParameters (“foo”);

p.AddNamedParameters (“foo”,42);

p.AddNamedParameters (“foo”, 42, 89, 73);



That is totally goodness, but the thing is they can do this with just the 3rd overload.   All those calls would work if you removed 1 and 2.  The only downside is that for its params support the C# compiler always creates a temporary array so that means without overloads 1 and 2 we’d get an extra array allocation for the first two call examples I show above.  


Now Rico might kill me for saying this, but that might not be a big deal at all.. creating an empty or one element array is VERY cheep, so cheep you don’t need to worry about it unless you expect the API to be used in a super tight loop.   And the benift to the platform of having two less APIs to document, test, explain, support, etc is well worth it.


So, my recommendation to the team will be to remove overloads 1 and 2 and just go with the 3rd overload unless there is some “tight loop” scenario for these APIs I am missing.  And if that is the case notice they need to specialize the entire implementation of 1 and 2.  That is 1 can’t just turn around create a temporary array or all you have done is masked the problem from developer… and that would clearly not be pit of success work.   


Oh and another thing – should all these really be virtual?  We want to minimize the number of virtual members to just what is needed for the extensibility we have designed into the platform.  This preserves our ability to innovate in the future.  In most overload cases it is sufficient to just make the most complicated overload virtual.  


What do you think, am I right or is the feature team going to “educate” me during the review?


Btw – I write this not to poke fun at the team bringing these APIs through, in fact I know those guys and they are doing an excellent job – building on the best parts of Longhorn (IMHO).  I write this in the hopes that one or two of you will find some useful nugget of information that helps you write\design better managed code.

Comments (11)

  1. Alan McBee says:

    I think this depends on whether you expect overload 1 or overload 3 to be the clearest way to the "pit of success." If you are trying to steer developers to use a pattern that predominately uses overload 1, then you should keep 1 and 3, and drop 2. If you are trying to steer developers to use a pattern that predominately uses overload 3, then drop 1 and 2, keeping only 3.

    Reasoning: Even forearmed with the knowledge (via Intellisense, perhaps) that the 2nd param to the overload is optional, some, perhaps quite a few, developers, will feel compelled to put it to use, perhaps to their own detriment. Maybe I’m unusual, but when I see the Intellisense pop up on a new function call, I use the arrow keys/buttons to roll through the list of possible overloads. If there is only one overload possible, I may or may not pay much attention to any "optional" markers. If there are two overloads, I’m probably much clearer that there is an option to using the 2nd+ params. When I’m uncertain which to choose, I’ll turn to the docs, where I expect to find more guidance on which is a best practice, or, if perf is an issue, I’ll construct a small perf test comparing all possible usages. Either way, I agree that I don’t see a compelling reason for the 2nd overload, other than it just sort of "feels" like it matches other overload patterns like this one that I’ve come across in the current framework.

    But I’m not necessarily your target developer, either.

  2. B. Tasker says:

    Might be a super-stupid question, but: If these three methods are good design and should be used in various places, why don’t you go with method number three and optimize the handling in the CLR, sort of compiler generated methods one and/or two?

  3. Sergey Korytnik says:

    Brad, while reading your colleagues blogs I got an impression that an excessive usage of short living garbage collected objects (this params array) could break garbage collector logic and lead to performance degradation. Some longer living objects could "age" faster due to presence of too many short living objects. Could these concerns be a good reason for existence of 3 API functions instead of one?

  4. Well, the BCL often uses multiple overloads to handle the case where 1 2 or 3 values is passed in. The only time the params case is used is when 4 or more list parameters need to go in. I was told the BCL does this for performance. It is faster to call the method that looks like:

    public virtual void AddNamedParameter(string parameterName, object parameterValue);

    public virtual void AddNamedParameter(string parameterName, object parameterValue, object parm2, object parm3);

    than it is to call

    public virtual void AddNamedParameter(string parameterName, params object[] parameterValues);

    I’m curious now, because this is a major design consideration for programming class library code.

  5. Brad Abrams says:

    Alan makes an interesting point… It does seem like something we can address with an IntelliSense feature…

    B. not a stupid question at all… that is basically what I am saying — only provide overload number 3.

    Sergey – yes, perf may be a reason to provide all three, but you should only do that if there is a common scenario to call this thing in an "inner loop". For most normal usage the GC is optimized to handle these small short lived objects.

    Justin — yes, the BCL in StringBuilder.Append() for example does provide several overloads not just params. This is a classic case of "inner loop". You can totally see building up a StringBuilder in a tight loop. in this case it is worth it to fully provide several implementations. Check out the Rotor sources for StringBuilder to see what I mean.

  6. Sergey Korytnik says:

    Well…the second attempt to guess 🙂

    What’s about C++? I believe that the 2nd overload will be used in at least 80% of cases. But I am not sure that even the forthcoming widely adverised C++/CLI binding will support the trick with params array. So C++ programmers need to create the array explicitly. But having the first and the second overloads would be enough for most of them. Anyway, C++ programmers don’t worry too much about multiple overloads. In contrast they are usually quite suspicious about functions having variable number of arguments.

  7. Brad Abrams says:

    utz — I am wrong. Check out the rotor source for StringBuilder:

    I still beleive the guideline is valid, just that we are not following it today in this place…

  8. Keith Patrick says:

    I’m all for whatever reduces the footprint of the API. IntelliSense can perform any user-friendly doctoring of the signature, and it guarantees that all roads lead to Rome, so to speak. String.Format is the one I remember the most as having a bit to many overloads where I have a hunch but can never quite remember for sure if all the variations of (string, object…) perform in the same manner, although most of my calls have enough params to where I know they’re going through params object[]

  9. Ken Brubaker says:

    For my own reference, I thought I’d compile a quick list of design guidelines added by Brad Abrams, et al.

  10. Aaron Meyers says:

    The reason that the framework provides the additional overloads is to make life easier for languages that ignore the "params" setting.

    I don’t remember what languages these are 🙂 But for example if C# didn’t support params, then the additional overloads allow you to do:

    string.Format("message{0}", value)

    string.Format("message{0} {1}", value0, value1)

    string.Format("message{0} {1} {2}", value0, value1, value2)

    You don’t have to switch to arrays until you get 4+ params:

    string.Format("message{0} {1} {2} {3}", new object[] { value0, value1, value2, value3 })