преобразовать перечисление в другой тип перечисления


120

У меня есть перечисление, например ' Gender' ( Male =0 , Female =1), и у меня есть другое перечисление из службы, у которой есть собственное перечисление Gender ( Male =0 , Female =1, Unknown =2)

Мой вопрос в том, как я могу написать что-то быстрое и красивое, чтобы преобразовать их перечисление в мое?


6
Во что вы хотите преобразовать «неизвестное»?
— Павел Минаев

Вы можете привести перечисление к другим типам перечислений, если оба имеют одинаковые значения, см. Ideone.com/7lgvgf
— Gowtham S

Ответы:


87

Использование метода расширения работает довольно аккуратно при использовании двух методов преобразования, предложенных Нейтом:

public static class TheirGenderExtensions
{
    public static MyGender ToMyGender(this TheirGender value)
    {
        // insert switch statement here
    }
}

public static class MyGenderExtensions
{
    public static TheirGender ToTheirGender(this MyGender value)
    {
        // insert switch statement here
    }
}

Очевидно, что нет необходимости использовать отдельные классы, если вы этого не хотите. Я предпочитаю, чтобы методы расширения были сгруппированы по классам / структурам / перечислениям, к которым они применяются.


234

Учитывая Enum1 value = ..., то, если вы имеете в виду по имени:

Enum2 value2 = (Enum2) Enum.Parse(typeof(Enum2), value.ToString());

Если вы имеете в виду числовое значение, вы обычно можете просто привести:

Enum2 value2 = (Enum2)value;

(с приведением вы можете использовать Enum.IsDefinedдля проверки допустимых значений)


16
Это лучший ответ
— Николай

1
Вот версия, которая использует Enum.Tryparse: Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown; Это позволит вам обрабатывать входные значения, которые не существуют, Enum2без необходимости вызывать Enum.IsDefinedили перехватывать ArgumentExceptionвыброшенные Enum.Parse. Обратите внимание, что порядок параметров более или менее обратный Enum.Parse.
— Sander

47

Просто приведите один к int, а затем приведите его к другому перечислению (учитывая, что вы хотите, чтобы отображение выполнялось на основе значения):

Gender2 gender2 = (Gender2)((int)gender1);

3
Хотя маловероятно увидеть это `` в дикой природе '', и это очень маловероятно для полов, может существовать какое-то перечисление, поддерживаемое long(или ulong), а не, для intкоторого определены члены, которые находятся выше int.MaxValue(или ниже int.MinValue), и в этом случае приведение к intможет переполниться, и вы получите неопределенное значение перечисления, которое должно быть определено.
— Rich O'Kelly

конечно. правильный способ будет (Gender2) ((вставьте здесь базовый тип) пол1), но я думаю, что приведенный выше пример дает правильную идею, поэтому я не буду его менять.
— Adrian Zanescu

3
Это требует, чтобы два перечисления имели одинаковые значения в одном порядке. Хотя он решает эту конкретную проблему, он действительно хрупкий, и я бы не стал использовать его для сопоставления перечислений в целом.
— sonicblis

2
ну .... ага! , Картирование нужно делать на основании чего-то. В этом случае отображение выполняется на целое значение. Для отображения базы по имени вам понадобится другой код. Для другого вида картографии что-нибудь еще. Никто не сказал, что это «для сопоставления перечислений в целом», и этот случай не существует, если вы не можете попытаться указать, что означает «сопоставление в целом»
— Адриан Занеску

20

Чтобы быть внимательным, я обычно создаю пару функций, одна из которых принимает Enum 1 и возвращает Enum 2, а другая принимает Enum 2 и возвращает Enum 1. Каждая из них состоит из оператора case, отображающего входы и выходы, а case по умолчанию генерирует исключение с сообщение с жалобой на неожиданное значение.

В этом конкретном случае вы могли бы воспользоваться тем фактом, что целочисленные значения Male и Female одинаковы, но я бы избегал этого, поскольку это хакерский и может быть поврежден, если одно из перечислений изменится в будущем.


7
+1 Я видел, как многие разработчики отказывались использовать целочисленные значения перечислений для их преобразования, но это очень чревато ошибками. Старый школьный метод написания двух функций со временем доказал свою ценность ...
— Хемант

20

Если у нас есть:

enum Gender
{
    M = 0,
    F = 1,
    U = 2
}

и

enum Gender2
{
    Male = 0,
    Female = 1,
    Unknown = 2
}

Мы можем безопасно делать

var gender = Gender.M;
var gender2   = (Gender2)(int)gender;

Или даже

var enumOfGender2Type = (Gender2)0;

Если вы хотите охватить случай, когда перечисление справа от знака '=' имеет больше значений, чем перечисление слева, вам придется написать свой собственный метод / словарь, чтобы охватить это, как предлагали другие.


Ваш ответ похож на вопрос !? Если да, то это не ответ, а если нет, есть аналогичный ответ выше ;).
— shA.t

13

Вы можете написать простой универсальный метод расширения, подобный этому

public static T ConvertTo<T>(this object value)            
    where T : struct,IConvertible
{
    var sourceType = value.GetType();
    if (!sourceType.IsEnum)
        throw new ArgumentException("Source type is not enum");
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Destination type is not enum");
    return (T)Enum.Parse(typeof(T), value.ToString());
}

1
Он не распространяется на случай пропущенных значений, как предложено в ответах выше. Вам следует изменить этот метод расширения, охватывающий и этот случай.
— eRaisedToX 02

8

вы можете написать простую функцию, подобную следующей:

public static MyGender ConvertTo(TheirGender theirGender)
{
    switch(theirGender)
    {
        case TheirGender.Male:
            break;//return male
        case TheirGender.Female:
            break;//return female
        case TheirGender.Unknown:
            break;//return whatever
    }
}

1
это не функция. ожидаемый "MyGender", а вы возвращаете "void"
— bl4ckr0se

7

Вот версия метода расширения, если кому интересно

public static TEnum ConvertEnum<TEnum >(this Enum source)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), source.ToString(), true);
    }

// Usage
NewEnumType newEnum = oldEnumVar.ConvertEnum<NewEnumType>();

Разве это не означает, что оба перечисления имеют одинаковые числовые значения?
— kuskmen

1
Нет, это преобразование по имени за строкой. Таким образом, Enum.Foo (1) преобразуется в Enum2.Foo (2), даже если их числовые значения различны.
— Джастин

3
public static TEnum ConvertByName<TEnum>(this Enum source, bool ignoreCase = false) where TEnum : struct
{
    // if limited by lack of generic enum constraint
    if (!typeof(TEnum).IsEnum)
    {
        throw new InvalidOperationException("enumeration type required.");
    }

    TEnum result;
    if (!Enum.TryParse(source.ToString(), ignoreCase, out result))
    {
        throw new Exception("conversion failure.");
    }

    return result;
}

2

Некоторое время назад я написал набор методов расширения, которые работают для нескольких различных типов Enums. Один, в частности, работает для того, что вы пытаетесь выполнить, и обрабатывает Enums FlagsAttributeкак Enumс, так и с различными базовыми типами.

public static tEnum SetFlags<tEnum>(this Enum e, tEnum flags, bool set, bool typeCheck = true) where tEnum : IComparable
{
    if (typeCheck)
    {
        if (e.GetType() != flags.GetType())
            throw new ArgumentException("Argument is not the same type as this instance.", "flags");
    }

    var flagsUnderlyingType = Enum.GetUnderlyingType(typeof(tEnum));

    var firstNum = Convert.ToUInt32(e);
    var secondNum = Convert.ToUInt32(flags);

    if (set)
        firstNum |= secondNum;

    else
        firstNum &= ~secondNum;

    var newValue = (tEnum)Convert.ChangeType(firstNum, flagsUnderlyingType);

    if (!typeCheck)
    {
        var values = Enum.GetValues(typeof(tEnum));
        var lastValue = (tEnum)values.GetValue(values.Length - 1);

        if (newValue.CompareTo(lastValue) > 0)
            return lastValue;
    }

    return newValue;
}

Оттуда вы можете добавить другие более конкретные методы расширения.

public static tEnum AddFlags<tEnum>(this Enum e, tEnum flags) where tEnum : IComparable
{
    SetFlags(e, flags, true);
}

public static tEnum RemoveFlags<tEnum>(this Enum e, tEnum flags) where tEnum : IComparable
{
    SetFlags(e, flags, false);
}

Это изменит типы Enums, как вы пытаетесь это сделать.

public static tEnum ChangeType<tEnum>(this Enum e) where tEnum : IComparable
{
    return SetFlags(e, default(tEnum), true, false);
}

Однако имейте в виду, что вы МОЖЕТЕ конвертировать между любым Enumи любым другим, Enumиспользуя этот метод, даже теми, у которых нет флагов. Например:

public enum Turtle
{
    None = 0,
    Pink,
    Green,
    Blue,
    Black,
    Yellow
}

[Flags]
public enum WriteAccess : short
{
   None = 0,
   Read = 1,
   Write = 2,
   ReadWrite = 3
}

static void Main(string[] args)
{
    WriteAccess access = WriteAccess.ReadWrite;
    Turtle turtle = access.ChangeType<Turtle>();
}

Переменная turtleбудет иметь значение Turtle.Blue.

Однако Enumпри использовании этого метода есть безопасность от неопределенных значений. Например:

static void Main(string[] args)
{
    Turtle turtle = Turtle.Yellow;
    WriteAccess access = turtle.ChangeType<WriteAccess>();
}

В этом случае accessбудет установлено значение WriteAccess.ReadWrite, посколькуWriteAccess Enum максимальное значение параметра равно 3.

Еще один побочный эффект от смешивания Enums с FlagsAttributeи без него заключается в том, что процесс преобразования не приведет к совпадению 1 к 1 между их значениями.

public enum Letters
{
    None = 0,
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    H
}

[Flags]
public enum Flavors
{
    None = 0,
    Cherry = 1,
    Grape = 2,
    Orange = 4,
    Peach = 8
}

static void Main(string[] args)
{
    Flavors flavors = Flavors.Peach;
    Letters letters = flavors.ChangeType<Letters>();
}

В этом случае lettersбудет иметь значение Letters.Hвместо вместо Letters.D, поскольку резервное значение Flavors.Peachравно 8. Кроме того, преобразование из Flavors.Cherry | Flavors.Grapeв Lettersможет дать результат Letters.C, что может показаться неинтуитивным.


2

Основываясь на ответе Джастина выше, я пришел к следующему:

    /// <summary>
    /// Converts Enum Value to different Enum Value (by Value Name) See https://stackoverflow.com/a/31993512/6500501.
    /// </summary>
    /// <typeparam name="TEnum">The type of the enum to convert to.</typeparam>
    /// <param name="source">The source enum to convert from.</param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    public static TEnum ConvertTo<TEnum>(this Enum source)
    {
        try
        {
            return (TEnum) Enum.Parse(typeof(TEnum), source.ToString(), ignoreCase: true);
        }
        catch (ArgumentException aex)
        {
            throw new InvalidOperationException
            (
                $"Could not convert {source.GetType().ToString()} [{source.ToString()}] to {typeof(TEnum).ToString()}", aex
            );
        }
    }

1

Я знаю, что это старый вопрос и на него есть много ответов, однако я считаю, что использование оператора switch, как в принятом ответе, несколько громоздко, поэтому вот мои 2 цента:

Мой личный любимый метод - использовать словарь, где ключ - это исходное перечисление, а значение - целевое перечисление, поэтому в случае, представленном в вопросе, мой код будет выглядеть так:

var genderTranslator = new Dictionary<TheirGender, MyGender>();
genderTranslator.Add(TheirGender.Male, MyGender.Male);
genderTranslator.Add(TheirGender.Female, MyGender.Female);
genderTranslator.Add(TheirGender.Unknown, MyGender.Unknown);

// translate their to mine    
var myValue = genderTranslator[TheirValue];

// translate mine to their
var TheirValue = genderTranslator .FirstOrDefault(x => x.Value == myValue).Key;;

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

public static class EnumTranslator
{

    private static Dictionary<TheirGender, MyGender> GenderTranslator = InitializeGenderTranslator();

    private static Dictionary<TheirGender, MyGender> InitializeGenderTranslator()
    {
        var translator = new Dictionary<TheirGender, MyGender>();
        translator.Add(TheirGender.Male, MyGender.Male);
        translator.Add(TheirGender.Female, MyGender.Female);
        translator.Add(TheirGender.Unknown, MyGender.Unknown);
        return translator;
    }

    public static MyGender Translate(this TheirGender theirValue)
    {
        return GenderTranslator[theirValue];
    }

    public static TheirGender Translate(this MyGender myValue)
    {
        return GenderTranslator.FirstOrDefault(x => x.Value == myValue).Key;
    }

}

Мне нравится этот подход, так как вы также можете перечислить оба перечисления, чтобы заполнить словарь. (конечно, когда они в одном порядке)
— AlexS

0

Вы можете использовать ToString () для преобразования первого перечисления в его имя, а затем Enum.Parse () для преобразования строки обратно в другое Enum. Это вызовет исключение, если значение не поддерживается перечислением назначения (например, для значения «Неизвестно»).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.