A frequent question I received concerning the Maybe static class was “why didn’t you make extension methods?” I am a proponent of extension methods, so it would seem that I would prefer “42”.ToInt32() over Maybe.ToInt32(“42”). However, I immediately created a static class for this despite my previous criticisms of helper classes. Why?
Semantics
What does it mean when someone writes Request.QueryString[“index”].ToInt32()? It’s not obvious that ToInt32() returns a Nullable<int>. When I first created the Maybe class, I wanted to call it Nullable but couldn’t do so because a non-generic Nullable existed in the framework. Maybe was the next best thing: it may be an Int32, in may be null.
Responsibility
There is another problem with making these extension methods. It is far too easy to give String too much responsibility. I try to be careful when making decisions about assigning anything to a string. I fully expect someone to create a full string domain specific language with extension methods in the near future.
Extension
Okay, so I did decide to make an extension method. I felt the methods on Maybe were candidates, and it would be easy enough to add “this” to the first parameter, but I wanted to maintain semantics. What I opted for was a Maybe<T> method on string.
public static Nullable<T> Maybe<T>(this string value) where T : struct
{
var maybe = new Dictionary<Type, Func<string, ValueType>>
{
{ typeof(BigInteger), s => Kodefu.Maybe.ToBigInteger(s) },
{ typeof(Boolean), s => Kodefu.Maybe.ToBoolean(s) },
{ typeof(Byte), s => Kodefu.Maybe.ToByte(s) },
{ typeof(DateTime), s => Kodefu.Maybe.ToDateTime(s) },
{ typeof(Decimal), s => Kodefu.Maybe.ToDecimal(s) },
{ typeof(Double), s => Kodefu.Maybe.ToDouble(s) },
{ typeof(Enum), s => Kodefu.Maybe.ToEnum<T>(s) },
{ typeof(Guid), s => Kodefu.Maybe.ToGuid(s) },
{ typeof(Int16), s => Kodefu.Maybe.ToInt16(s) },
{ typeof(Int32), s => Kodefu.Maybe.ToInt32(s) },
{ typeof(Int64), s => Kodefu.Maybe.ToInt64(s) },
{ typeof(SByte), s => Kodefu.Maybe.ToSByte(s) },
{ typeof(Single), s => Kodefu.Maybe.ToSingle(s) },
{ typeof(TimeSpan), s => Kodefu.Maybe.ToTimeSpan(s) },
{ typeof(UInt16), s => Kodefu.Maybe.ToUInt16(s) },
{ typeof(UInt32), s => Kodefu.Maybe.ToUInt32(s) },
{ typeof(UInt64), s => Kodefu.Maybe.ToUInt64(s) },
};
Type type = typeof(T);
type = type.IsEnum ? type.BaseType : type;
if (maybe.ContainsKey(type))
{
return maybe[type](value) as Nullable<T>;
}
return null;
}
** I updated the code base to move the dictionary out of the method. Creating a dictionary was unnoticeable in the operation, but I suspect someone will complain. grab the updated version of you don’t like this one.
This started out as one big set of if statements. However, it was nearly unreadable so I refactored to a dictionary. The fully qualified domain name it necessary because the method name is Maybe<T>, and there are conflicts even though the Maybe static class has no generic parameter. Which brings me to another C# lesson: you cannot add this as an extension method in the Maybe class because it has the same name as the class. I put it under String extensions.
If you’re wondering about the ValueType and the odd cast… well, without generic inference, the compiler couldn’t figure out that I was returning a Nullable<T>. I decided to cast in one place.
If you prefer the extension method way of doing things, you can call Request.QueryString[“index”].Maybe<int>() ?? 0. I still prefer helper class way of doing things, but it’s an option!
Download the source code at kodefu.codeplex.com.