Mapping without the switch command

by Alex Siepman 21. July 2013 16:57

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

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

I am a software architect at Roxit and also a C# Developer. My main interests in the area of ​​C# are LINQ and generics

Visit my personal homepage (Dutch) for more info about me.

Month List

Page List