

Using implicit operator overloads to keep your C# API discoverable and open - danielwertheim
http://danielwertheim.se/2014/05/02/c-using-implicit-operator-overloads-to-keep-your-api-discoverable-and-open/

======
Locke1689
Do not do this. There's no reason to not use an enum here -- requiring a cast
is not a bad thing.

There are very few cases where you should have implicit conversions. Implicit
conversions basically imply that the two types are so similar that no possible
semantic difference can be observed after the conversion. For example, int is
implicitly convertible to long because there can be no possible information
loss and almost every case where long is allowed, an int would also be allowed
in that place. Even overload resolution works correctly because the overload
is first tried without the implicit conversion.

Enums (or other structs), on the other hand, can easily have different
semantics. For example, if the enum values are auto-assigned and you add a
value into the middle of the enum, all subsequent values are now offset-
increased by 1. If you then switch on the int values, but not the enum values,
your semantics will change.

Here an explicit cast is a good thing -- it's saying to the compiler that I'm
doing something potentially dangerous, but just trust me on this one.

------
philbarr
Not sure what the point of the implicit operator overloads was. Why not just
have:

    
    
      public class CreateBooking {
        public int TypeId { get; private set; }
        ...
        public CreateBooking(BookingTypeId typeId, ..., ...) {
            TypeId = typeId.Value;
            ...
        }
      }
    
      public struct BookingTypeId {
        private readonly int _value;
    
        private BookingTypeId (int value) {
            //...some simple validation logic...
            _value = value;
        }
    
        public int Value { get { return _value; } }
    
        public static BookingTypeId Custom(int value) {
            return new BookingTypeId(value);
        }
    
        public static BookingTypeId Gold() {
            return new BookingTypeId(3);
        }
    
        public static BookingTypeId Silver() {
            return new BookingTypeId(2);
        }
    
        public static BookingTypeId Bronze() {
            return new BookingTypeId(1);
        }
      }
    

With the added bonus that you're using less fancy stuff, so you don't need to
explain to the junior dev that gets to maintain it what's going on.

~~~
danbruc
I would prefer static read-only properties over methods and always return the
same instance.

    
    
        private static readonly BookingTypeId gold = new BookingTypeId(3);
    
        public static BookingTypeId Gold
        {
           get { return BookingTypeId.gold; }
        }
    

It becomes a bit inconsistent because of the method for the custom case but I
would accept that for less noise in the common case.

~~~
kevingadd
If the field is readonly why did you make the property in the first place?
Just do public static readonly BookingTypeId Gold. It's equivalent.

~~~
Locke1689
If this is public surface area, the change from field to property is a binary
break.

~~~
danbruc
...and you never know when you might have to add something to the getter. It's
the same reason why you usually prefer properties over public fields even if
there is no logic in the accessors. Similarly you usually want to expose
constants by wrapping them in a read-only property because otherwise the
compiler will inline the constant and you can not change it without
recompiling all dependencies.

------
swalsh
I like where his head is at, he's trying to make code easier to write in a
type safe way i guess. But this is a bit over zealous.

An enum IS the right solution here. Its more maintainable because everyone who
touches the code base will implicitly know how the code works. With the
solution he used, as i'm reading the code... i'm going to go "oh hey, huh i
wonder how that works", then i'll spend 10 minutes trying to understand the
code, added to the extra 10 minutes probably spent writing this code, I think
its a bad idea.

if you're worried about passing a bad value in still, i think its fine to add
a range check in the constructor, and to throw an exception. Exceptions are
not something to be afraid of.

Also, i don't know what library he's using... but i feel somewhat strongly
like i've done something like this in the past where i've serialized JSON, and
enums have serialized as integers (as long as entry assigned a numeric
value?). So it wouldn't be an issue to pass it in as a enum anyways.

------
andrea_s
So, this pretty much works like an enum that can extended with additional
numeric values on the fly.

But doesn't this implementation kind of defy the purpose of having an enum-
like functionality in the first place?

~~~
pradocchia
It still provides some of the same ergonomics. You can put away the mental
cheat-sheet ("1: gold, 2: silver...."). Your IDE can find all uses of
BookTypeId.Gold(), rather than textual occurrences of "1". Etc.

The static quality of enums is only one of their features, and sometimes a
hindrance, and it good to have other options.

~~~
andrea_s
sure, but I can't see how this is supposed to increase discoverability of the
API...

I see "here is something that looks like an enum, and its possible values are
gold, silver and bronze. But you can also give 10, 42 or any number as a value
and it will still work".

I don't mean to be overly critical (clearly this approach has worked well for
the author), but I am a bit unsure about the perceived benefits.

~~~
pradocchia
Yeah, I'm not sure how much it increase discoverability, plus users pay the
cognitive cost of quirky patterns.

I might have used an enum plus a second constructor to wrap the explicit
conversation. I guess it depends on what's going on in the rest of the API.

------
guiomie
"Enums would demand an explicit cast and they are static. Also, any logic to
them, would have to be added as extension methods, and in the case I had,
logic tied to the BookingTypeId was arround the corner."

Can someone help me understand what he means here? Thanks

~~~
louthy
> Enums would demand an explicit cast
    
    
        enum Test
        {
            A,B,C
        }
    
        int x = Test.A;  // This won't assign 
    
        int x = (int)Test.A;  // This will
    

> and they are static

Not sure why that's important.

> Also, any logic to them, would have to be added as extension methods

You can extend enums using extension methods, just like you can with classes
or structs (you can also extend delegates FYI).

> and in the case I had, logic tied to the BookingTypeId was arround the
> corner.

No idea.

~~~
klauserc
>> Enums would demand an explicit cast > > enum Test > { > A,B,C > } > > int x
= Test.A; // This won't assign > > int x = (int)Test.A; // This will

The OP wants the first one to work (that requires an implicit cast). The
second one is an explicit cast. You explicitly demand a conversion to int.

> > and they are static > > Not sure why that's important. You cannot add more
> values to the enum without modifying the enum. If you don't own code to the
> Assembly that defines the enum, you're out of luck. With the OP's solution,
> a third-party dev could add more 'constants' to "TypeId", simply by
> providing more factory methods.

Sure those factory methods would not be located on the "TypeId" type, but
that's acceptable.

>> Also, any logic to them, would have to be added as extension methods

> You can extend enums using extension methods, just like you can with classes
> or structs (you can also extend delegates FYI).

Yes, but you can't add properties, for instance. Also extension methods are a
C# and VB compiler feature. Other .NET languages (especially dynamic ones)
will not discover these methods. Same goes for uses of 'dynamic' and
reflection.

~~~
louthy
You seem to be shooting the messenger here? I'm not advocating any method, I
was merely trying to help decipher the original message.

------
MichaelGG
Doesn't this come off a bit as overkill? Especially adding the comparison
interfaces. In F# you'd do something like:

    
    
      type TypeId = TypeId of int
        with 
          static member Gold = TypeId 1 
    

Custom is now just "TypeId 123". Otherwise, TypeId.Gold. (This is assuming you
don't want to specify the cases directly as union cases).

You can also abstract this to a general phantom type like "type Id<'a> = Id of
int". Now functions can ask for an Id<'a> for arbitrary types. Sort of like a
poor version of the units-of-measurement.

This article's kind of boilerplate OO stuff seems like it's out of control.
Over the past week, I've been using open source clients in .NET to connect to
social media services. And a common thread is to write oh-so-much code and
provide tons of types just to get a simple job done.

One Twitter client lib requires _7_ binaries. Types are split across around 50
namespaces. And of course, every type has its own file.

------
computerslol
I think I see where you're coming from. Correct me if I am wrong.

If I were solving the same problem:

public void CreateBooking( int chosenBookingType,... ){ ...

\- and -

public string[] GetAvailableBookingTypes(){ ...

or something similar.

Your implementer can call GetAvailableBookingTypes at run-time to populate
whatever interface lets you select one, then pop the index of the selection
back into CreateBooking. I have it returning an array of strings as a very
simple solution; you can just as easily return an array of objects that better
represent your booking types (containing descriptions, and URIs to icon images
or colors or whatever). The methods are named in such a way as to lend to the
fact that they are related. No enums, no wonkiness and you can add new booking
types at will without having to let your implementer know something changed.
If your implementers are not catching on that the two methods are related, you
can solve that problem with easy to read documentation.

------
mdpm
The only justification for not using fixed static values here (ala enums - an
enumeration of possible values) is the potential case of having values that
are dynamic at run-time, rather than design-time.

Being as that the article consistently refers to these being fixed values, and
that they're optimising for 3rd party use of their library, that doesn't seem
to be the case.

This seems to be the classic case of a solution looking for a problem.

------
danielwertheim
Thanks for all the feedback. Sweet. Tried to answer some Q I have been getting
from various directions here: [http://danielwertheim.se/2014/05/12/reply-to-c-
using-implici...](http://danielwertheim.se/2014/05/12/reply-to-c-using-
implicit-operator-overloads-to-keep-your-api-discoverable-and-open/)

Thanks,

//Dan

------
ehosca
this is a stupid idea. you're not ready to publish an API - let alone be
concerned with discoverability if you have no idea what you're publishing.

