7

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

鉴于以下情况:

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

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

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

示例 #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

4

7 回答 7

4

您还可以使用扩展方法将您的 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
}
于 2011-12-01T15:18:21.890 回答
3

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));
        }
    }
}
于 2010-02-04T03:36:17.177 回答
1

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

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];
于 2010-02-03T16:12:54.453 回答
1

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

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

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

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

于 2010-02-03T16:01:26.887 回答
0

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

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

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

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

于 2010-02-03T15:52:05.813 回答
0
  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
     }
  }
于 2010-02-03T16:09:16.037 回答
-1

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

编辑: 澄清......假设您有多个声明转换规则的委托,其中一个是默认的(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 时,随时返回用于执行映射的委托。

于 2010-02-03T15:54:09.133 回答