Mapping without the switch command

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);
              }
          }
      }
      

Leave a Comment

Comment

Comments