在C#中,如何轻松地将枚举标志从一种类型映射到另一种类型?

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

另请参阅问题末尾的更新…

考虑到以下情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[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,
}

我想将一个枚举类型转换为另一个枚举类型,反之亦然,基于使用类似于big switch()的名称的映射函数,但由于这是一个标志枚举的标记,因此我很难将此类例程设计为泛型。

基本上,我想要的是如下:

例1

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

例2

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

例3

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

例4

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

例5

1
2
3
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!"));

map()函数可以接受一个委托来提供实际的映射,但是我仍然需要几个函数来帮助这样一个委托使用位…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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!");
    }
}

编辑:澄清

枚举定义的值似乎彼此匹配,但情况并非如此。

例如,它可以是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,
}

编辑:更多信息

我正在寻找一种对枚举标志进行自定义映射的方法,而不是基于名称上的模式。但是,这些名称在自定义映射函数中使用。

我完全可以有一个sourcetodestmapper函数尝试将sa映射到dc,例如…

主要问题是向sourcetodestmapper函数提供源的每个标志,并处理具有多个位集的标志值…

例如:具有sourceEnum.sabc标志将调用sourceTodesMapper函数三次,结果如下:

  • sourceEnum.sa映射到desteEnum.da
  • sourceEnum.sb映射到desteEnum.db
  • sourceEnum.sc映射到desteEnum.dc

结果destenum为:destenum.da destenum.db destenum.dc


还可以使用扩展方法将sourceEnum转换为desteEnum,下面是一些单元测试的代码

或者使用另一个伟大的工具,如ValueInjecter:http://valueinjecter.codeplex.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 [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
}

这里有一个解决方案,它只需要一个映射字典,并通过扫描它来执行映射。不幸的是,System.Enum不能用作泛型约束,因此我使用处理强制转换的特定派生类构建了解决方案。

请注意,flagmapper的构造函数接受相互映射的单个标志对。它还可以将多个位映射到多个位,只要您确保映射都是一致的。如果在源枚举中对的第一个元素中的所有位都是开的,那么对的第二个元素中的位将在目标枚举中设置。

sall到dall的映射目前无法工作,因为在我的构造函数中,我没有映射高阶位。我没有做这个映射,因为它与SD映射失败的要求有点不一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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));
        }
    }
}


我认为,如果枚举的名称遵循类似的模式,那么沿着这些行的某些内容就可以工作了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
    }

选项2是建立一个ints字典来进行映射。

1
2
3
4
5
6
7
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];


1
2
3
4
5
6
7
8
9
10
11
12
  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
     }
  }


如果枚举的值在逻辑上是相等的,则可以将一个枚举强制转换为另一个枚举,例如

1
2
3
public DestEnum Map(SourceEnum source) {
    return (DestEnum)SourceEnum;
}

如果是这种情况,您可以只使用两个带有const int成员的静态类。

但是,如果SourceEnum.SA在逻辑上等同于DestEnum.DCSourceEnum.SAB==DestEnum.SomethingElse,那么您除了编写自定义映射之外别无选择。


不知道为什么需要这样做,因为看起来您实际上只需要一个枚举。

如果假设"等效"枚举值的数值始终相同是安全的,那么真正的问题是"所提供的值是否设置了不属于目标枚举的任何"标志"。一种方法是循环遍历目标枚举的所有可能值。如果设置了flas,则从值XOR它。如果有价值!在循环结束时为0,则无法转换。

如果可以转换,那么只需将值强制转换为int,然后再转换为新类型。

附言:我有没有提到一开始做这件事很奇怪?


通用字典是以哈希表的形式实现的,因此算法的复杂性是O(1)。所以如果枚举非常大,它是最快的方法。

编辑:澄清…假设您有多个委托声明转换规则,其中一个是默认的(sa->da),让我们命名为:默认的_委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
    }

因此,当调用mymapper.mapdelegate时,随时返回执行映射的delegate。