API design: choosing between non-ideal options

So, UnconstrainedMelody is coming on quite nicely. It now has quite a few useful options for flags enums, "normal enums" and delegates. However, there are two conflicting limitations which leave a couple of options. (Other related answers on Stack Overflow have suggested alternative approaches, basically.)

Currently, most of the enums code is in two classes: Flags and Enums. Both are non-generic: the methods within them are generic methods, so they have type parameters (and constraints). The main benefit of this is that generic type inference only applies to generic methods, and I definitely want that for extension methods and anywhere else it makes sense.

The drawback is that properties can’t be generic. That means my API is entirely expressed in terms of methods, which can be a pain. The option to work around this is to have a generic type which properties in. This adds confusion and guesswork – what call is where?

To recap, the options are:

// Option 1 (current): all methods in a nongeneric class:
// Some calls which are logically properties end up
// as methods…
IList<Foo> foos = Enums.GetValues<Foo>();
// Type infererence for extenion methods
// Note that we couldn’t have a Description property
// as we don’t have extension properties
string firstDescription = foos[0].GetDescription();
        
// Option 2: Use just a generic type:
// Now we can use a property…
IList<Foo> foos = Enums<Foo>.Values;
// But we can’t use type inference
string firstDescription = Enums<Foo>.GetDescription(foos[0]);
        
// Option 3: Use a mixture (Enums and Enums<T>):
IList<Foo> foos = Enums<Foo>.Values;
// All looks good…
string firstDescription = foos[0].GetDescription();
// … but the user has to know when to use which class

All of these are somewhat annoying. If we only put extension methods into the nongeneric class, then I guess users would never need to really think about that – they’d pretty much always be calling the methods via the extension method syntactic sugar anyway. It still feels like a pretty arbitrary split though.

Any thoughts? Which is more important – conceptual complexity, or the idiomatic client code you end up with once that complexity has been mastered? Is it reasonable to make design decisions like this around what is essentially a single piece of syntactic sugar (extension methods)?

(By the way, if anyone ever wanted justification for extension properties, I think this is a good example… Description feels like it really should be a property.)

10 thoughts on “API design: choosing between non-ideal options”

  1. What criterion are you using to determine that GetValues() should be a property?

    I don’t see a problem with it being a method. It wouldn’t even have occurred to me to make it a property.

  2. As a user of the API, I’d prefer Option 1 for the simplicity and explorability. Even though Description should be a property, we all know how to use a Get/Set method pair.

    You might want to make a note in the doc’s remarks explaining why it was done that way, though. :)

  3. @wcoenan: It doesn’t return an array though. There’s a separate method which explicitly does that. It returns an immutable list – and so it’s returning the same value on every occurrence. That’s why it would be reasonable to be a property. The XML documentation explicitly says that the list returned is immutable.

    (I’ve considered renaming GetValues() to GetValuesList() to avoid confusion with the existing method in Enum…)

    Jon

  4. System.Linq.Enumerable has a similar issue with its Empty method. I do forget the parens sometimes, but I would still choose option 1 over sacrificing inference or splitting the class.

    Consider how it reads out loud:
    “Enums, get values of Foo.”
    or,
    “Enums of Foo, get values.”

  5. I think putting only the extension methods into the non-generic class would still be discoverable enough. It is a bit of an arbitrary split, but I’d prefer the consuming code to look as clean and logical as possible (though not at the expense of discoverability, but as previously mentioned, I don’t think discoverability would be harmed too much).

  6. In a library I would give precedence to use over creation always…

    Since discoverability for the extension methods does not require the name, only the namespace, making it a separate class makes sense.

    In truth I would think that few people would use the properties via fully qualified names so simply importing the main namespace gives you IDE discoverability for the extensions and puts the Enums class there in easy reach.

  7. I think I would prefer option 1, because it takes advantage of generic type inference.

    The only reason for preferring properties would be for data binding scenarios (properties can be bound to, methods can’t). However I can’t think of a useful binding scenarios involving UnconstrainedMelody, so that reason is probably not enough to tip the scales…

  8. I would vote for option 1. Making Values into a property is not particularly compelling for me; I understand the argument from a strict component-oriented-design perspective, but in this case I think GetValues() is just as natural (although I admit that may be because I am used to Enum.GetValues).

Comments are closed.