I often find myself writing or consuming API's which require the caller to specify some sort of options.  I've seen numerous ways to specify those options, but I've yet to find one that I really like.  Let's work with an example throughout the rest of this post.  Imagine we're working on an application which has some notion of a User.  We're now about to create a function to output a User to a Stream.  We want to provide two options:

  1. Should we include the User's email address?
  2. Should we include the User's phone number?

I can imagine several ways of designing this API.  First, we could use a couple of bool parameters, like this:

 

void DisplayUser(User user, TextWriter stream, bool displayEmail, bool displayPhoneNumber)

Next, we could use a flags enum to represent the options, like this:

[Flags]
enum DisplayUserOptions
{
    Email = 0x1,
    PhoneNumber = 0x2,
    Both = (Email | PhoneNumber),
}
void DisplayUser(User user, TextWriter stream, DisplayUserOptions options)

Finally we could create a struct which contains two bools instead of the enum, like so:

struct DisplayUserOptions
{
    public bool Email;
    public bool PhoneNumber;
}
void DisplayUser(User user, TextWriter stream, DisplayUserOptions options)

I don't like passing the two bools, because I find that reading the callsite of a method designed like this to be difficult, because you don't know what the parameters mean anymore.

DisplayUser(user, Console.Out, true, false);

Does this display the email address or the phone number?  I can't remember anymore.  I know that parameter help can tell you the names of the parameters, but that doesn't help when I'm doing a code review in windiff, since windiff doesn't have parameter help tool-tips yet.

On my current team, we have a convention that when we call a method like this, we put the parameter name in a comment, so that we can see it, like:

DisplayUser(user, Console.Out, true /*displayEmail*/, false /*displayPhoneNumber*/);

However, in general, I don't like to rely on conventions that force people to put comments in a certain style to make the code readable.  If possible, I'd rather design the API in such a way that it has to be readable.

The second option is using enum flags to represent the options.

DisplayUser(user, Console.Out, DisplayUserOptions.Email);

My problem with this approach is that it is hard to get right, both for the caller of the API, and the implementer.  This example is sort of trivial, but I can remember a time when I was dealing with a bit-field that contained 31 unique bit values that could be set and unset independently.  Getting all of the ~ and | and &'s just right was very hard, and once it was done, it was hard to figure out what it was trying to do.  A final reason that I don't like enums is that in practice I often find that there are more behaviors that I want to be able to add to the options which isn't possible with enums.  For example, it might be a requirement that two of the options are mutually exclusive.  It's difficult to ensure that this is enforced with an enum.

Finally, we have the option of using a struct, which is addresses both of the two concerns above.  You can write a call like:

 

DisplayUserOptions options = new DisplayUserOptions();
options.Email = true;
options.PhoneNumber = false;
DisplayUser(user, Console.Out, options);

Well, that's certainly clear.  It also makes it easier to understand control flow that sets or clears the options, and to understand conditions based on them.  It also gives a place to add that behavior: I can add methods to the struct, make the fields into properties which have setters that do validation, etc.

The problem with this approach is that in simple cases like the above, it is much more verbose than either of the other two alternatives.  However, I recently realized that in C# 3.0, we can take advantage of Object Initializers to make the simple case simple again:

DisplayUser(user, Console.Out, new DisplayUserOptions { Email = true, PhoneNumber = false });

It's almost like having named parameters in C# :)

What do you think?  Which alternative do you prefer?  Do you have another one that I haven't thought of?