In a project I had to map a lot of external codes and values to our codes and values. This results in methods with switch statements:
private int GetInternalCode(string externalCode)
{
var result = 0;
switch (externalCode)
{
case "A":
result = 0;
break;
case "B":
result = 2;
break;
case "C":
result = 5;
break;
default:
result = 6;
break;
}
return result;
}
This is a lot of plumbing code so I decided to write some mapping extensions. The above code can now be written like this.
private int GetInternalCode(string externalCode)
{
return externalCode
.Map("A", 0)
.Map("B", 2)
.Map("C", 5)
.Else(6);
}
This a lot easier to write and maintain. The mapping extensions are also more flexible. It can uses functions to select the right value.
private int GetExternalCode(int internalCode)
{
return internalCode
.Map(0, -1)
.Map(IsEven, 1)
.Map(x => x > 100, 2)
.Map(51, 3)
.Else(4);
}
And it uses functions to define the result value:
private int GetExternalCode(int internalCode)
{
return internalCode
.Map(0, -1)
.Map(IsEven, GetDoubleValue)
.Map(x => x > 100, -2)
.Else(x => x + 2);
}
private int GetDoubleValue(int value)
{
return value * 2;
}
With the Visual Basic Select Case statement, you can use ranges and lists. With 2 handy extra extensions, you can do the same.
The extra extensions class:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Blog.Mapping
{
public static class OtherExtensions
{
public static bool IsInRange<T>(this T source, T minimum, T maximum) where T : IComparable<T>
{
return source.CompareTo(minimum) >= 0 && source.CompareTo(maximum) <= 0;
}
public static bool IsIn<T>(this T source, params T[] values)
{
return values.Contains(source);
}
}
}
New mapping options:
private int GetExternalCode(int internalCode)
{
return internalCode
.Map(x => x.IsIn(1, 3, 8, 12, 13), 1)
.Map(x => x.IsIn(20,25,30), 2)
.Map(x => x.IsInRange(30, 50), 3)
.Else(4);
}
If an "else value" is prohibited, you can easily throw an exception (or something else like some logging code).
private int GetInternalCode(string externalCode)
{
return externalCode
.Map("A", 0)
.Map("B", 2)
.Map("C", 5)
.ElseDo(x =>
{
throw new ArgumentException("The following value can not be mapped:" + x, "externalCode");
});
}
Below you find the code of the 2 classes that make this way of mapping possible:
1: A normal class
using System;
using System.Collections.Generic;
namespace Blog.Mapping
{
public class MapResult<TValue, TResult>
{
private TValue OriginalValue { get; set; }
public TResult Result { get; private set; }
private bool MatchedPreviously { get; set; }
internal MapResult(TValue value, TResult result = default(TResult), bool matchedPreviously = false)
{
OriginalValue = value;
Result = result;
MatchedPreviously = matchedPreviously;
}
public MapResult<TValue, TResult> Map(TValue ifValue, TResult thenValue)
{
return Map(x => x.Equals(ifValue), x => thenValue);
}
public MapResult<TValue, TResult> Map(TValue ifValue, Func<TValue, TResult> thenFunc)
{
return Map(x => x.Equals(ifValue), thenFunc);
}
public MapResult<TValue, TResult> Map(Func<TValue, bool> ifFunc, TResult thenValue)
{
return Map(ifFunc, x => thenValue);
}
public MapResult<TValue, TResult> Map(Func<TValue, bool> ifFunc, Func<TValue, TResult> thenFunc)
{
if (MatchedPreviously || !ifFunc(OriginalValue))
{
return this;
}
var result = new MapResult<TValue, TResult>(OriginalValue, thenFunc(OriginalValue), true);
return result;
}
public TResult Else(TResult elseValue)
{
return Else(x => elseValue);
}
public TResult Else(Func<TValue, TResult> elseFunc)
{
if (MatchedPreviously)
{
return Result;
}
var result = elseFunc(OriginalValue);
return result;
}
public TResult ElseDo(Action<TValue> doThis)
{
if (MatchedPreviously)
{
return Result;
}
doThis(OriginalValue);
return Result;
}
public static implicit operator TResult(MapResult<TValue, TResult> value)
{
return value.Result;
}
}
}
2: And the extension class
using System;
namespace Blog.Mapping
{
public static class MapExtensions
{
public static MapResult<TValue, TResult> Map<TValue, TResult>(this TValue originalValue, TValue ifValue, TResult thenValue)
{
return new MapResult<TValue, TResult>(originalValue).Map(ifValue, thenValue);
}
public static MapResult<TValue, TResult> Map<TValue, TResult>(this TValue originalValue, Func<TValue, bool> ifFunc, TResult thenValue)
{
return new MapResult<TValue, TResult>(originalValue).Map(ifFunc, thenValue);
}
public static MapResult<TValue, TResult> Map<TValue, TResult>(this TValue originalValue, TValue ifValue, Func<TValue, TResult> thenFunc)
{
return new MapResult<TValue, TResult>(originalValue).Map(ifValue, thenFunc);
}
public static MapResult<TValue, TResult> Map<TValue, TResult>(this TValue originalValue, Func<TValue, bool> ifFunc, Func<TValue, TResult> thenFunc)
{
return new MapResult<TValue, TResult>(originalValue).Map(ifFunc, thenFunc);
}
}
}