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 |
如果枚举的值在逻辑上是相等的,则可以将一个枚举强制转换为另一个枚举,例如
1 2 3 | public DestEnum Map(SourceEnum source) { return (DestEnum)SourceEnum; } |
如果是这种情况,您可以只使用两个带有
但是,如果
不知道为什么需要这样做,因为看起来您实际上只需要一个枚举。
如果假设"等效"枚举值的数值始终相同是安全的,那么真正的问题是"所提供的值是否设置了不属于目标枚举的任何"标志"。一种方法是循环遍历目标枚举的所有可能值。如果设置了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。