В C #, как легко отобразить флаги перечисления от одного типа к другому? - PullRequest
6 голосов
/ 03 февраля 2010

Также смотрите обновления в конце вопроса ...

Учитывая следующую ситуацию:

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

Я хотел бы преобразовать один тип перечисления в другой и наоборот, основываясь на функции отображения, используя имена, такие как большой переключатель (), но так как это перечисление flags, мне трудно создать такую ​​подпрограмму, чтобы она была универсальной .

По сути, я хочу что-то вроде следующего:

Пример # 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,
}

РЕДАКТИРОВАТЬ: Подробнее

Я ищу способ сделать пользовательское отображение флагов enum, не основанное на шаблонах имен. Однако имена используются в пользовательской функции сопоставления.

Я вполне мог бы иметь функцию SourceToDestMapper, пытающуюся сопоставить SA с DC, например ...

Основная проблема заключается в предоставлении функции SourceToDestMapper каждого флага источника И заботе о значениях флагов, имеющих несколько наборов битов ...

Например: Наличие флага SourceEnum.SABC вызовет функцию SourceToDestMapper три раза, что приведет к следующему:

  • SourceEnum.SA сопоставлен с DestEnum.DA
  • SourceEnum.SB сопоставлен с DestEnum.DB
  • SourceEnum.SC сопоставлен с DestEnum.DC

И в результате DestEnum будет: DestEnum.DA | DestEnum.DB | DestEnum.DC

Ответы [ 7 ]

2 голосов
/ 04 февраля 2010

Вот решение, которое просто берет словарь сопоставлений и выполняет сопоставление, просматривая его. К сожалению, 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));
        }
    }
}
2 голосов
/ 01 декабря 2011

вы также можете использовать методы расширения для преобразования вашего 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
}
1 голос
/ 03 февраля 2010

Я думаю, что что-то в этом роде будет работать, если предположить, что имена перечислений следуют похожему шаблону:

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];
0 голосов
/ 03 февраля 2010
  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
     }
  }
0 голосов
/ 03 февраля 2010

Если значения ваших перечислений логически эквивалентны, вы можете просто привести одно перечисление к другому, например,

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

Если это так, вы можете просто использовать пару статических классов с const int членов.

Однако, если SourceEnum.SA логически эквивалентно DestEnum.DC или SourceEnum.SAB == DestEnum.SomethingElse, то вам не остается иного выбора, кроме как написать собственное отображение.

0 голосов
/ 03 февраля 2010

Общий словарь реализован как хеш-таблица, следовательно, сложность алгоритма составляет O (1).Поэтому, если enum довольно большой, это самый быстрый способ.

EDITED: Чтобы уточнить ... Предположим, у вас есть несколько делегатов, которые объявляют правило преобразования, где ОДИН из них является значением по умолчанию (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 возвращает удаление в любое время для выполнения сопоставления.

0 голосов
/ 03 февраля 2010

Не знаю, зачем вам это нужно, поскольку кажется, что вам действительно нужно одно перечисление.

Если можно с уверенностью предположить, что числовые значения «эквивалентных» значений перечисления всегда одинаковы, тогда реальный вопрос заключается в том, «имеет ли предоставленное значение какой-либо набор« флагов », которые не являются частью целевого перечисления» , Один из способов сделать это - перебрать все возможные значения для целевого перечисления. Если вспышка установлена, то xor ее от значения. Если значение! = 0 в конце цикла, то оно не может быть преобразовано.

Если его можно преобразовать, просто приведите значение к типу int, а затем к новому типу.

PS. Я уже говорил, что это странно, во-первых, сделать это?

...