关于c#:将枚举转换为另一种类型的枚举

convert an enum to another type of enum

我有一个枚举,例如"Gender"(Male =0 , Female =1),我有另一个枚举,它来自一个具有自己的性别枚举(Male =0 , Female =1, Unknown =2)的服务。

我的问题是,如何快速而漂亮地写一些东西,将它们从枚举转换为我的枚举?


给定Enum1 value = ...,如果您的意思是:

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

如果您的意思是数值,您通常可以强制转换:

1
Enum2 value2 = (Enum2)value;

(对于强制转换,您可能希望使用Enum.IsDefined检查有效值)


当使用Nate建议的两种转换方法时,使用扩展方法的效果相当好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    }
}

显然,如果不想使用单独的类,就不需要使用单独的类。我的首选是按应用于的类/结构/枚举对扩展方法进行分组。


只需将一个强制转换为int,然后将其强制转换为另一个枚举(考虑到您希望基于值完成映射):

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


为了彻底起见,我通常创建一对函数,一个接受枚举1并返回枚举2,另一个接受枚举2并返回枚举1。每个都包含一个case语句,将输入映射到输出,默认case抛出一个异常,并显示一条消息,抱怨意外的值。

在这个特定的例子中,您可以利用这样一个事实:男性和女性的整数值是相同的,但是我会避免这种情况,因为如果将来两个枚举中的任何一个发生变化,它都是黑客的,并且会受到破坏。


如果我们有:

1
2
3
4
5
6
enum Gender
{
    M = 0,
    F = 1,
    U = 2
}

1
2
3
4
5
6
enum Gender2
{
    Male = 0,
    Female = 1,
    Unknown = 2
}

我们可以安全地做到

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

甚至

1
var enumOfGender2Type = (Gender2)0;

如果要覆盖"="符号右侧的枚举值比左侧的枚举值多的情况,则必须编写自己的方法/字典来覆盖其他人建议的方法/字典。


您可以编写一个这样的简单的通用扩展方法

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
6
7
8
9
10
11
12
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
2
3
4
5
6
7
public static TEnum ConvertEnum<TEnum >(this Enum source)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), source.ToString(), true);
    }

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


前一段时间我编写了一套扩展方法,它适用于几种不同类型的Enums。其中一种方法特别适用于您试图完成的工作,并使用FlagsAttribute和具有不同基础类型的Enum处理Enums。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
}

从那里可以添加其他更具体的扩展方法。

1
2
3
4
5
6
7
8
9
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);
}

这一个会像你想做的那样改变Enum的类型。

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

不过,请注意,您可以使用此方法在任何Enum和任何其他Enum之间进行转换,即使那些没有标志的方法也是如此。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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值。例如:

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

在这种情况下,access将被设置为WriteAccess.ReadWrite,因为WriteAccessEnum的最大值为3。

Enums与FlagsAttribute和不使用FlagsAttribute的混合的另一个副作用是转换过程不会导致它们的值之间的1对1匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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>();
}

在这种情况下,由于Flavors.Peach的支持值为8,因此letters的值将为Letters.H,而不是Letters.D。另外,从Flavors.Cherry | Flavors.Grapeletters的转换将产生Letters.C,这可能看起来不具有必然性。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

根据上面贾斯汀的回答,我想到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /// <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
            );
        }
    }


我知道这是一个古老的问题,有很多答案,但是我发现在接受的答案中使用switch语句有点麻烦,所以这里是我的2分:

我个人最喜欢的方法是使用字典,其中键是源枚举,值是目标枚举,所以在这个问题上,我的代码看起来像这样:

1
2
3
4
5
6
7
8
9
10
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;;

当然,可以将其包装在静态类中,并用作扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
    }

}


可以使用ToString()将第一个枚举转换为其名称,然后使用Enum.Parse()将字符串转换回另一个枚举。如果目标枚举不支持该值(即对于"未知"值),则会引发异常。