【问题标题】:In C#, how to easily map enum flags from one type to another?在 C# 中,如何轻松地将枚举标志从一种类型映射到另一种类型?
【发布时间】:2010-02-03 15:44:37
【问题描述】:

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

鉴于以下情况:

[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,
}

我想将一种枚举类型转换为另一种枚举类型,反之亦然.

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

示例 #1

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

示例 #2

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

示例 #3

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

示例 #4

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

示例 #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!"));

Map() 函数可以接受一个委托来提供实际映射,但我仍然需要有几个函数来帮助这样一个委托处理这些位......

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!");
    }
}

编辑:澄清

枚举定义的值似乎相互吻合,但不一定如此。

例如,它可能是:

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 将调用 SourceToDestMapper 函数 3 次,结果如下:

  • SourceEnum.SA 映射到 DestEnum.DA
  • SourceEnum.SB 映射到 DestEnum.DB
  • SourceEnum.SC 映射到 DestEnum.DC

结果 DestEnum 将是:DestEnum.DA |目标枚举.DB |目标枚举.DC

【问题讨论】:

    标签: c# enums mapping bit-fields


    【解决方案1】:

    您还可以使用扩展方法将您的 SourceEnum 转换为 DestEnum,这是代码带有一些单元测试

    或使用其他出色的工具,例如 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
    }
    

    【讨论】:

      【解决方案2】:

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

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

      SALL 到 DALL 的映射目前不起作用,因为在我的构造函数中我没有映射高阶位。我没有做这个映射,因为它有点不符合 SD 映射失败的要求。

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

      【讨论】:

      • 接近...正如您所写,唯一缺少的是处理 SALL 到 DALL 的映射。
      • 通过添加{ (int)SourceEnum.SALL, (int)DestEnum.DALL },SALL 到 DALL 的映射应该可以正常工作。但是,您必须删除 SD 的故障映射,或者在没有设置 SD 位的情况下定义 SALL。
      • 实际上,以不同的方式处理故障映射可能会更好。如果字典中没有故障映射,则同一字典可用于正向和反向映射。
      【解决方案3】:

      如果你的枚举值在逻辑上是等价的,你可以将一个枚举转换为另一个,例如

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

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

      但是,如果 SourceEnum.SA 在逻辑上等同于 DestEnum.DC 或 SourceEnum.SAB == DestEnum.SomethingElse,那么您别无选择,只能编写自定义映射。

      【讨论】:

      • 没错,我需要一个自定义映射。但是,我正在寻找一种通用的方法......
      【解决方案4】:

      假设枚举的名称遵循类似的模式,我认为这些方法会起作用:

      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 是设置一个整数字典来进行映射..

      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];
      

      【讨论】:

      • 好吧,如果你不能基于名称或值,那么你只需要手动映射它们。
      • 此时我认为您处于手动映射领域。但是使用整数字典似乎是更好的选择..
      【解决方案5】:

      不知道你为什么需要这样做,因为看起来你真的只需要一个枚举。

      如果可以安全地假设“等效”枚举值的数值始终相同,那么真正的问题是“提供的值是否设置了任何不属于目标枚举的‘标志’” .一种方法是循环遍历目标枚举的所有可能值。如果设置了 flas,则将其与值异或。如果循环结束时值 != 0,则无法转换。

      如果可以转换,则只需将值转换为 int,然后转换为新类型。

      PS。我有没有提到一开始这样做很奇怪?

      【讨论】:

      • 这可能看起来很奇怪,但这样一个函数的原因是将来自其他程序集的 Enum 映射到我自己的程序集中定义的 Enum 之间。此外,从一个枚举到另一个枚举的实际值不一定相同。只有在一些帮助下进行“手动”映射。
      • @Stecy,这是您应该在问题中包含的那种细节。
      【解决方案6】:
        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
           }
        }
      

      【讨论】:

      • 不会工作,因为例如SA 应该映射到具有不同值的 DA。
      • @Havard - 什么?这会将 SA 更改为 DA,因为它们是相同的值,请检查他的初始枚举。如果它们不同,它将抛出他想要的异常
      • 查看说明。需要映射例如SA 到 DA,但他们不一定共享价值观。
      【解决方案7】:

      通用字典被实现为哈希表,因此算法的复杂度为 O(1)。因此,如果 enum 相当大,这是最快的方法。

      已编辑: 澄清一下...假设您有多个声明转换规则的委托,其中一个是默认的(SA->DA),我们命名为: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;
          }
      

      所以当调用 MyMapper.MapDelegate 时,随时返回用于执行映射的委托。

      【讨论】:

      • 我不确定我是否理解你的回答...你能详细说明一下吗?
      • 这在这种情况下不起作用,除非您愿意将每个标志组合添加到字典中
      • @Stecy - 我已经用示例更新了帖子(抱歉,它包含语法错误)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-11
      • 2011-05-30
      • 1970-01-01
      • 1970-01-01
      • 2021-04-18
      • 1970-01-01
      • 2016-06-27
      相关资源
      最近更新 更多