views:

499

answers:

6

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

A: 

Not sure why you need to do this, since it seems like you really just need one enumeration.

If it is safe to assume that the numeric values of the "equivalent" enum values are always the same, then the real question is "does the supplied value have any 'flags' set which are not part of the target enumeration". One way to do this would be to loop through all possible values for the target enumeration. If the flas is set, then xor it from the value. If the value != 0 at the end of the loop, then it cannot be converted.

If it can be converted, then just cast the value to an int and then to the new type.

PS. Have I mentioned that it is strange to do this in the first place?

Chris
It may seems strange but the reason for such a function is to map between an Enum from some other assembly to an Enum defined in my own assembly. Also, the actual values are not necessarily the same from one enum to another. Only a "manual" mapping with some help would do.
Stecy
@Stecy, this is the kind of detail you should include in the question.
runrunraygun
A: 

generic Dictionary is implemented as hashtable, hence complexity of algorithm is O(1). So if enum rather large it is most fast way.

EDITED: To clarify... Assume you have multiple delegates that declares rule of conversion, where ONE of them is default (SA->DA), let's name: default_delegate.

class MyMapper
{
    delegate DestEnum singlemap(SourceEnum);
    static Dictionary<SourceEnum, singlemap> _source2dest = 
       new Dictionary<SourceEnum, singlemap>();
    static MyMapper()
    {
         //place there non-default conversion
         _source2dest[S_xxxx] = My_delegate_to_cast_S_xxxx;
         ......
    }
    static singlemap MapDelegate(SourceEnum se)
    {
        singlemap retval;
        //checking has complexity O(1)
        if(_source2dest.TryGetValue ( se, out retval) )
            return retval;
        return default_delegate;
    }

So when calling MyMapper.MapDelegate returns anytime deleage for performing mapping.

Dewfy
I am not sure if I understand your answer... Could you elaborate?
Stecy
This doesn't work in this case, unless you're willing to add every combination of the flags into the dictionary
David Kemp
@Stecy - I've updated post with sample (sorry it contain syntax errors)
Dewfy
A: 

If the values of your enums are logically equivalent, you can just cast one enum to the other, eg

public DestEnum Map(SourceEnum source) {
    return (DestEnum)SourceEnum;
}

If this is the case, you could just use a couple of static classes with const int members.

However, if SourceEnum.SA is logicially equivalent to DestEnum.DC, or SourceEnum.SAB == DestEnum.SomethingElse, then you've not choice but to write a custom mapping.

David Kemp
Exactly, I would need a custom mapping. However, I am looking at a way of doing it kind of generically...
Stecy
A: 
  public ReturnEnum ConvertEnum<InEnum, ReturnEnum>(InEnum fromEnum)
  {
     ReturnEnum ret = (ReturnEnum)Enum.ToObject(typeof(ReturnEnum), fromEnum);
     if (Enum.IsDefined(typeof(ReturnEnum), ret))
     {
        return ret;
     }
     else
     {
        throw new Exception("Nope"); // TODO: Create more informative error here
     }
  }
SwDevMan81
Won't work since e.g. SA should map to DA, which has a different value.
Håvard S
@Havard - What? This will change SA to DA because they are the same value, check his initial enums. If they are different, it will throw an exception like he wanted
SwDevMan81
See the clarification. It's desired to map e.g. SA to DA, but they don't necessarily share values.
Håvard S
Ok, so you down vote me because he changed his mind on what he wants...
SwDevMan81
Well, the clarification came 10 minutes before your suggestion...
Håvard S
Sorry I like to test before I put out my code
SwDevMan81
+1  A: 

I think something along these lines would work, assuming that the names of the enums follow a similar pattern:

public D Map<D, S>(S enumValue, D defaultValue)
    {

        D se = defaultValue; 
        string n = Enum.GetName(typeof(S), enumValue);

        string[] s = Enum.GetNames(typeof(S));
        string[] d = Enum.GetNames(typeof(D));
        foreach (var v in d)
        {
            if (n.Substring(1, n.Length - 1) == v.Substring(1, v.Length - 1))
            {
                se = (D)Enum.Parse(typeof(D), v);
                break;
            }
        }
        return se;
    }

Options 2 would be to set up a dictionary of ints to do the mapping..

DestEnum de = DestEnum.DNone;
        SourceEnum se = SourceEnum.SA;
        Dictionary<int, int> maps = new Dictionary<int, int>();
        maps.Add((int)SourceEnum.SNone, (int)DestEnum.DNone);
        maps.Add((int)SourceEnum.SAB, (int)(DestEnum.DA | DestEnum.DB));
        maps.Add((int)SourceEnum.SA, (int)DestEnum.DA);
        de = (DestEnum)maps[(int)se];
chris.w.mclean
Unfortunately, I cannot base the mapping on part of the names either...
Stecy
Well if you cant base it on the name or the value, then youll just need to manually map them.
SwDevMan81
At this point I think you're in manual mapping land. But using a dictionary of ints seems like the better option..
chris.w.mclean
+1  A: 

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));
        }
    }
}
Dave
Getting close... The only thing missing, as you've written, is to handle the SALL to DALL mapping.
Stecy
The SALL to DALL mapping should work fine by adding `{ (int)SourceEnum.SALL, (int)DestEnum.DALL }`. However you'll have to remove the fail-mapping for SD, or define SALL without the SD bit set.
Dave
Actually, it may be better to handle to failure mappings in a different way. Without the failure mappings in the dictionary the same dictionary could be used for both forward and reverse mappings.
Dave