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