danielwertheim

danielwertheim


notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".

Share


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

C# - Using implicit operator overloads to keep your API discoverable and open

Daniel WertheimDaniel Wertheim

Recently I had the need to provide consumers of a SDK with a set of possible values that could be passed to a constructor of a member in a SDK library. The "list" of values was semi-static. With this, I mean that I also needed to treat this list of possible values as a "current version" and still allow the consumer to pass a somewhat arbitrary value. Why? Because the remote service could come to allow more values before the SDK has had a chance to be updated. There are (as always) many solutions, but lets look at one using simple factory methods and a implicit operator overloads.

The integration with the remote service is done using JSON. The command’s/request’s public getters are serialized and should preferably contain "primitive" types. Lets say we have a simple command/request object for creating a booking:

public class CreateBooking {  
    public int TypeId { get; private set; }
    ...
    public CreateBooking(int typeId, ..., ...) {
        TypeId = typeId;
        ...
    }
}

The possible values of TypeId are currently as follows:

Gold: 3  
Silver: 2  
Bronze: 1  

This could easily be solved with some simple "key-master" with constants. Or a simple enum, but thats not what we will pick. Instead, we will offer simple factory methods on a custom type BookingTypeId which will be used like this:

public class CreateBooking {  
    public int TypeId { get; private set; }
    ...
    public CreateBooking(BookingTypeId typeId, ..., ...) {
        TypeId = typeId;
        ...
    }
}

A custom type being assigned to an integer? How? Easy. By the use of operator overloads. But before we take a look at them, lets answer the obvious questions:

Why not an enum or a key-master?

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.

Key-masters, like a static class with public constants, are not my favorite. Why? Bad discoverability. Because the argument to the constructor would then have to be an int, and there’s no easy way to hint the consumer of possible values as there is with a custom type. With this in mind, lets have a look at the solution.

The solution

First, we create a custom type that will be acceptet by the constructor of the CreateBooking class. By accepting a custom type, we give a clear hint of where to start looking for possible values.

public struct BookingTypeId {  
    private readonly int _value;

    private BookingTypeId (int value) {
        //...some simple validation logic...
        _value = 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);
    }
}

The constructor is being kept private because I want to force the user to discover the factory methods. To be able to pass "arbitrary" values I also provide a factory method for Custom values.

The final piece will be to have it implicitly convertible to anint.

public struct BookingTypeId {  
    ...

    public static implicit operator int(BookingTypeId item) {
        return item._value;
    }
}

And while at it, lets add some interfaces to make it equatable and comparable to make the tests assertions work seamlesly.

public struct BookingTypeId : IEquatable<int>, IComparable<int> {  
    ...

    public bool Equals(int other) {
        return _value.Equals(other);
    }

    public int CompareTo(int other) {
        return _value.CompareTo(other);
    }
}

That’s it! Done. Solved a problem for me. We can now do:

var goldBooking = new CreateBooking(BookingTypeId.Gold(), ..., ...);  
//or
var customBooking = new CreateBooking(BookingTypeId.Custom(42), ..., ...);  

That was all. Worked in my scenario. Not sure if you have any direct use for it right now. Perhaps something to put in the toolbox though?

Cheers,

//Daniel

Developer that lives by the mantra "code is meant to be shared".

Comments