Преобразовать помеченное перечисление в другое - PullRequest
0 голосов
/ 01 июля 2019

У меня есть общее перечисление, скажем, G, которое имеет некоторые помеченные значения (Один = 0 / Два = 1 / Три = 2 / Четыре = 4 / Пять = 8 и т. Д.).

Затем у меня есть другое перечисление (скажем, B), которое «расширяет» G с помощью такого типа паттерна: One = G.One / Two = G.Two / Three = G.Three / Four = G.Four (и это все, нет пять в этом).

У меня наконец есть последнее перечисление (скажем, C), которое также «расширяет» G с тем же типом шаблона, но с другими значениями: Three = G.Three / Four = G.Four / Five = G.Five (no One и два в этом).

Я бы хотел найти универсальную функцию для преобразования B в C или C в B. Например, если у меня есть «A valsAsA = A.One | A.Three | A.Four», мне нужна функция, подобная этой: «B valsAsB = convert (valsAsA);» это дало бы мне "B.Three | A.Four".

Это должно быть действительно общим, потому что у меня есть не только перечисления A и B, но также C, D, E ... с различными возможными значениями перечисления, но всегда значения из обобщенного перечисления.

Возможно ли, не проверяя все возможности и не адаптируя функцию каждый раз, когда я добавляю новый enum?

Пример:

    public enum General : int
    {
        One = 0,
        Two = 1,
        Three = 2,
        Four = 4,
        Five = 8
    }

    public enum A : int
    {
        One = General.One,
        Two = General.Two,
        Three = General.Three,
        Four = General.Four,
    }

    public enum B : int
    {
        Three = General.Three,
        Four = General.Four,
        Five = General.Five
    }

    public enum C : int
    {
        One = General.One,
        Three = General.Three,
        Five = General.Five
    }

    public class Test
    {
        public void testConvert()
        {
            A valAsA = A.One | A.Three | A.Four;
            B valAsB = convertFct(valAsA); // Should give me "B.Three | B.Four"
            C valAsC = convertFct(valAsA); // Should give me "C.One | C.Three"
        }
    }

Я проверял это:

A valAsA = A.One | A.Three | A.Four; 
C valAsC = (C)valAsA;
C valAsCReal = C.One | C.Three; // expected result

без удачи .. valAsC = 6, а valAsCReal = 2 ...

Большое спасибо

Ответы [ 2 ]

2 голосов
/ 02 июля 2019

Делать это с обобщениями немного сложно, потому что невозможно установить ограничение типа, которое допускает перечисления вообще (см. этот вопрос ). Лучшее, что вы можете сделать, это ограничиться struct, IConvertible и выполнить проверку во время выполнения, как я делаю в этом примере.

Если вы можете справиться с этой частью безобразия, все остальное довольно просто:

Сначала напишите два метода преобразования в General и обратно. Поскольку ваши перечисления являются битовыми масками, «преобразование» на самом деле является просто двоичной операцией and против суммы всех возможных значений, которую вы можете получить, используя GetValues ​​.

После того, как вы выполнили операцию и, вы можете вернуть перечисление соответствующего типа, преобразовав целое число с помощью Enum.ToObject () .

static public class ExtensionMethods
{
    static public General ToGeneral<T>(this T input) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Input must be an enum.");

        return (General)((int)(object)input & Enum.GetValues(typeof(General)).Cast<int>().Sum());
    }

    static public T ToEnum<T>(this General input)
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Output type must be an enum.");

        return (T)Enum.ToObject(typeof(T), (int)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
    }
}

После того, как они написаны, преобразование в любое перечисление легко и просто:

static public TOut Convert<TIn,TOut>(TIn input) where TIn : struct, IConvertible where TOut: struct, IConvertible
{
    var general = input.ToGeneral();
    return general.ToEnum<TOut>();
}

Тестовый код:

public static void Main()
{
    A valAsA = A.One | A.Three | A.Four;
    B valAsB = Convert<A, B>(valAsA);  // Should give me "B.Three | B.Four"
    C valAsC = Convert<A, C>(valAsA); // Should give me "C.One | C.Three"

    Console.WriteLine("{0} should equal {1}", valAsB, (B.Three | B.Four));
    Console.WriteLine("{0} should equal {1}", valAsC, (C.One | C.Three));
}

Вывод:

6 should equal 6
Three should equal Three

См. Код в действии на DotNetFiddle

0 голосов
/ 01 июля 2019

Блин !!!Я мог бы создать эту невероятную функцию, которая ответит на мой вопрос, но, пожалуйста ... скажи мне, что есть что-то более элегантное ... xD

        private TRet ConvertIt<TRet, TOrig>(TOrig values) where TOrig : struct, IConvertible where TRet : struct, IConvertible
        {
            if (!typeof(TOrig).IsEnum || 
                !typeof(TRet).IsEnum ||
                !typeof(int).IsAssignableFrom(typeof(TOrig)) || 
                !typeof(int).IsAssignableFrom(typeof(TRet)))
            {
                throw new ArgumentException("TOrig and TRet must be an enumerated type extending integer");
            }

            bool retEnumHasZero = false;

            foreach (var flag in Enum.GetValues(typeof(TRet)))
            {
                if ((int)flag == 0)
                {
                    retEnumHasZero = true;
                    break;
                }
            }

            if (!retEnumHasZero)
            {
                throw new ArgumentException("TRet enum must have the 0 flag");
            }

            Dictionary<int, Enum> valsOrig = new Dictionary<int, Enum>();

            foreach (var flag in Enum.GetValues(typeof(TOrig)))
            {
                valsOrig.Add((int)flag, (Enum)flag);
            }

            object valuesAsObject = values;
            var valuesAsEnum = (Enum)valuesAsObject;


            int returnedValue = 0;

            foreach (var flag in Enum.GetValues(typeof(TRet)))
            {
                int flagAsInt = (int)flag;

                if (valsOrig.ContainsKey(flagAsInt) && valuesAsEnum.HasFlag(valsOrig[flagAsInt]))
                {
                    returnedValue |= flagAsInt;
                }
            }

            return (TRet)Enum.ToObject(typeof(TRet), returnedValue);
        }

Используя функцию:

A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C, A>(valAsA);

Изменить: Эта реализация выглядит лучше:

        private T ConvertIt<T>(Enum values) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new ArgumentException("Type to return must be an enumerated type");
            }

            if (!Enum.IsDefined(typeof(T), 0))
            {
                throw new ArgumentException("Type to return enum must have the 0 flag");
            }

            int returnedValue = 0;

            foreach (var flag in Enum.GetValues(values.GetType()))
            {
                int flagAsInt = (int)flag;

                if (values.HasFlag((Enum)flag) && Enum.IsDefined(typeof(T), flagAsInt))
                {
                    returnedValue |= flagAsInt;
                }
            }

            return (T)Enum.ToObject(typeof(T), returnedValue);
        }

Использование функции:

A valAsA = A.One | A.Two | A.Three | A.Four;
C valAsC = ConvertIt<C>(valAsA);

Наконец, с помощью Джона Ву, вот последняя функция =>

        private T ConvertIt<T>(Enum input) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new ArgumentException("Type to return must be an enumerated type");
            }

            return (T)Enum.ToObject(typeof(T), (int)(object)input & Enum.GetValues(typeof(T)).Cast<int>().Sum());
        }
...