Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C#, how to easily map enum flags from one type to another?

Also see the updates at the end of the question...

Given the following situation:

[Flags]
enum SourceEnum
{
    SNone = 0x00,

    SA = 0x01,
    SB = 0x02,
    SC = 0x04,
    SD = 0x08,

    SAB = SA | SB,

    SALL = -1,
}

[Flags]
enum DestEnum
{
    DNone = 0x00,

    DA = 0x01,
    DB = 0x02,
    DC = 0x04,

    DALL = 0xFF,
}

I would like to convert one enum type to the other and vice-versa based on mapping function using the names like a big switch() but since this a flags enum I am having a difficult time designing such a routine to be generic.

Basically, what I want is something like the following:

Example #1

SourceEnum source = SourceEnum.SA;
DestEnum dest = Map<Source, Dest> (source);
Assert.That (dest, Is.EqualTo (DestEnum.DA));

Example #2

SourceEnum source = SourceEnum.SA | SourceEnum.SB;
DestEnum dest = Map<Source, Dest> (source);
Assert.That (dest, Is.EqualTo (DestEnum.DA | DestEnum.DB));

Example #3

SourceEnum source = SourceEnum.SAB;
DestEnum dest = Map<Source, Dest> (source);
Assert.That (dest, Is.EqualTo (DestEnum.DA | DestEnum.DB));

Example #4

SourceEnum source = SourceEnum.SALL;
DestEnum dest = Map<Source, Dest> (source);
Assert.That (dest, Is.EqualTo (DestEnum.DALL));

Example #5

SourceEnum source = SourceEnum.SD;
var ex = Assert.Throws<Exception> (() => Map<Source, Dest> (source));
Assert.That (ex.Message, Is.EqualTo ("Cannot map SourceEnum.SD to DestEnum!"));

The Map() function could accept a delegate for providing the actual mapping but I still need to have several functions for helping such a delegate with the bits...

DestEnum SourceToDestMapper (SourceEnum source)
{
    // Switch cannot work with bit fields enumeration...
    // This is to give the general idea...
    switch (source)
    {
        case SourceEnum.SNone:
            return DestEnum.DNone;

        case SourceEnum.SA:
            return DestEnum.DA;

        case SourceEnum.SAB:
            return DestEnum.DA | DestEnum.DB;

        ...

        default:
            throw new Exception ("Cannot map " + source.ToString() + " to DestEnum!");
    }
}

EDIT: CLARIFICATION

The values of the enum definitions might seem to fit between each others but that is not necessarily the case.

For example, it could be:

enum SourceEnum
{
    SA = 0x08,
    SB = 0x20,
    SC = 0x10,
    SAB = SA | SB,
    SABC = SA | SB | SC,
}

enum DestEnum
{
    DA = 0x04,
    DB = 0x80,
    DC = 0x01,
    DAB = DA | DB,
}

EDIT: More info

I am looking at a way of doing a custom mapping of enum flags, not based on patterns on the names. However, the names are used in the custom mapping function.

I would be perfectly possible to have a SourceToDestMapper function trying to map SA to DC for example...

The main problem is feeding the SourceToDestMapper function with each flag of the source AND taking care of flag values having multiple bit sets...

For example: Having a flag SourceEnum.SABC would call the SourceToDestMapper function three times resulting in the following:

  • SourceEnum.SA mapped to DestEnum.DA
  • SourceEnum.SB mapped to DestEnum.DB
  • SourceEnum.SC mapped to DestEnum.DC

And the resulting DestEnum would be: DestEnum.DA | DestEnum.DB | DestEnum.DC

like image 597
Stécy Avatar asked Feb 03 '10 15:02

Stécy


People also ask

What does << mean in C?

<< is the left shift operator. It is shifting the number 1 to the left 0 bits, which is equivalent to the number 1 .

What is && operator in C?

The && (logical AND) operator indicates whether both operands are true. If both operands have nonzero values, the result has the value 1 . Otherwise, the result has the value 0 . The type of the result is int . Both operands must have an arithmetic or pointer type.


2 Answers

you can also use Extension Methods to transform you SourceEnum to DestEnum, here's the code with some unit tests

or use another great tool such as ValueInjecter : http://valueinjecter.codeplex.com/

 [Flags]
public enum SourceEnum
{
    SA = 0x08,
    SB = 0x20,
    SC = 0x10,
    SAB = SA | SB,
    SABC = SA | SB | SC
}
[Flags]
public enum DestEnum
{
    DA = 0x04,
    DB = 0x80,
    DC = 0x01,
    DAB = DA | DB
}
public static class ExtensionTests
{

    public static SourceEnum ToSourceEnum(this DestEnum destEnum)
    {
        SourceEnum toSourceEnum=0x0;
        if ((destEnum & DestEnum.DA) == DestEnum.DA)
            toSourceEnum |= SourceEnum.SA;
        if ((destEnum & DestEnum.DB) == DestEnum.DB)
            toSourceEnum |= SourceEnum.SB;
        if ((destEnum & DestEnum.DC) == DestEnum.DC)
            toSourceEnum |= SourceEnum.SC;

        return toSourceEnum;
    }
    public static DestEnum ToDestEnum(this SourceEnum sourceEnum)
    {
        DestEnum toDestEnum=0;
        if ((sourceEnum & SourceEnum.SA) == SourceEnum.SA)
            toDestEnum = toDestEnum | DestEnum.DA;
        if ((sourceEnum & SourceEnum.SB) == SourceEnum.SB)
            toDestEnum = toDestEnum | DestEnum.DB;
        if ((sourceEnum & SourceEnum.SC) == SourceEnum.SC)
            toDestEnum = toDestEnum | DestEnum.DC;

        return toDestEnum;
    }
}


/// <summary>
///This is a test class for ExtensionMethodsTest and is intended
///to contain all ExtensionMethodsTest Unit Tests
///</summary>
[TestClass()]
public class ExtensionMethodsTest
{
    #region Sources
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SA_inverts()
    {
        //then you code goes like this...
        SourceEnum sourceEnum = SourceEnum.SA;
        Assert.AreEqual(SourceEnum.SA, sourceEnum.ToDestEnum().ToSourceEnum(), "");
        //and vice-versa...
    }

    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SAB_inverts()
    {
        //then you code goes like this...
        SourceEnum sourceEnum = SourceEnum.SAB;
        Assert.AreEqual(SourceEnum.SAB, sourceEnum.ToDestEnum().ToSourceEnum());
        //and vice-versa...
    }
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SABC_inverts()
    {
        //then you code goes like this...
        SourceEnum sourceEnum = SourceEnum.SABC;
        Assert.AreEqual(SourceEnum.SABC, sourceEnum.ToDestEnum().ToSourceEnum());
        //and vice-versa...
    }
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SA_Union_SC_inverts()
    {
        //then you code goes like this...
        SourceEnum sourceEnum = SourceEnum.SA | SourceEnum.SC;
        Assert.AreEqual(SourceEnum.SA | SourceEnum.SC, sourceEnum.ToDestEnum().ToSourceEnum());
        //and vice-versa...
    }
    #endregion

    #region Source To Destination
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SA_returns_DestEnum_DA()
    {
        Assert.IsTrue(DestEnum.DA == SourceEnum.SA.ToDestEnum());
    }
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SAB_returns_DestEnum_DAB()
    {
        Assert.IsTrue(DestEnum.DAB == SourceEnum.SAB.ToDestEnum());
    }
    [TestMethod]
    public void ExtensionMethodsTest_SourceEnum_SA_SC_returns_DestEnum_DA_DC()
    {
        Assert.IsTrue((DestEnum.DA | DestEnum.DC) == (SourceEnum.SA | SourceEnum.SC ).ToDestEnum());
    }

    #endregion

    #region Destination to Source
     [TestMethod]
    public void ExtensionMethodsTest_DestEnum_SA_returns_SourceEnum_DA()
    {
        Assert.IsTrue(SourceEnum.SA == DestEnum.DA.ToSourceEnum());
    }
     [TestMethod]
    public void ExtensionMethodsTest_DestEnum_SAB_returns_SourceEnum_DAB()
    {
        Assert.IsTrue(SourceEnum.SAB == DestEnum.DAB.ToSourceEnum());
    }
     [TestMethod]
    public void ExtensionMethodsTest_DestEnum_SABC_returns_SourceEnum_DAB_DC()
    {
        Assert.IsTrue(SourceEnum.SABC == (DestEnum.DAB | DestEnum.DC ).ToSourceEnum());
    }

    #endregion
}
like image 138
zobidafly Avatar answered Oct 20 '22 04:10

zobidafly


Here is a solution that simply takes a dictionary of mappings and performs a mapping by scanning over it. Unfortunately System.Enum can't be used as a generic constraint so I've built up the solution using a specific derived class that handles the casting.

Note that the constructor for FlagMapper takes pairs of single flags that map to each other. It can also map multiple bits to multiple bits so long as you ensure the mappings are all consistent. If all of the bits in the first element of the pair are on in the source enum, then the bits in the second element of the pair will be set in the destination enum.

The mapping for SALL to DALL currently wont work because in my constructor I haven't mapped the higher order bits. I didn't make this mapping because it's kind of inconsistent with the requirement that the mapping of SD fail.

using System;
using System.Collections.Generic;

namespace Flags
{
    [Flags]
    enum SourceEnum
    {
        SNone = 0x00,

        SA = 0x01,
        SB = 0x02,
        SC = 0x04,
        SD = 0x08,

        SAB = SA | SB,

        SALL = -1,
    }

    [Flags]
    enum DestEnum
    {
        DNone = 0x00,

        DA = 0x01,
        DB = 0x02,
        DC = 0x04,

        DALL = 0xFF,
    }

    class FlagMapper
    {
        protected Dictionary<int, int> mForwardMapping;

        protected FlagMapper(Dictionary<int, int> mappings)
        {
            this.mForwardMapping = mappings;
        }

        protected int Map(int a)
        {
            int result = 0;

            foreach (KeyValuePair<int, int> mapping in this.mForwardMapping)
            {
                if ((a & mapping.Key) == mapping.Key)
                {
                    if (mapping.Value < 0)
                    {
                        throw new Exception("Cannot map");
                    }

                    result |= mapping.Value;
                }
            }

            return result;
        }
    }

    class SourceDestMapper : FlagMapper
    {
        public SourceDestMapper()
            : base(new Dictionary<int, int>
            {
                { (int)SourceEnum.SA, (int)DestEnum.DA },
                { (int)SourceEnum.SB, (int)DestEnum.DB },
                { (int)SourceEnum.SC, (int)DestEnum.DC },
                { (int)SourceEnum.SD, -1 }
            })
        {
        }

        public DestEnum Map(SourceEnum source)
        {
            return (DestEnum)this.Map((int)source);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SourceDestMapper mapper = new SourceDestMapper();

            Console.WriteLine(mapper.Map(SourceEnum.SA));
            Console.WriteLine(mapper.Map(SourceEnum.SA | SourceEnum.SB));
            Console.WriteLine(mapper.Map(SourceEnum.SAB));

            //Console.WriteLine(mapper.Map(SourceEnum.SALL));

            Console.WriteLine(mapper.Map(SourceEnum.SD));
        }
    }
}
like image 26
Dave Avatar answered Oct 20 '22 05:10

Dave