将一个枚举类型转换为另一种枚举类型。

183

我有一个枚举类型,比如 'Gender' (Male =0 , Female =1),另外我还有一个服务中的枚举类型,它有自己的 Gender 枚举类型 (Male =0 , Female =1, Unknown =2)

我的问题是如何快速而好地编写代码将其枚举类型转换为我的枚举类型?


7
你想将“未知”转换成什么? - Pavel Minaev
1
当两个枚举类型具有相同的值时,您可以将枚举类型强制转换为其他枚举类型,请参见http://ideone.com/7lgvgf。 - Gowtham S
如果这只在Java中能够工作就好了... - devinbost
18个回答

329

如果给定 Enum1 value = ...,那么如果您是指名称:

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

如果你指的是数值类型,通常可以直接进行强制类型转换:
Enum2 value2 = (Enum2)value;

(针对转换类型后,您可能需要使用 Enum.IsDefined 检查有效值。)

24
这是更好的答案。 - Nicholas
9
这是使用 Enum.Tryparse 的一个版本:Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown;这将允许您处理 Enum2 中不存在的输入值,而无需调用 Enum.IsDefined 或捕获 Enum.Parse 抛出的 ArgumentException。请注意,参数的顺序与 Enum.Parse 相比更多或更少被颠倒了。 - Sander
是的,依我之见,这应该是被接受的答案。 - Sonali Agrawal

106

使用扩展方法非常方便,当使用 Nate 建议的两种转换方法时:

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

如果您不想使用单独的类,显然没有必要这样做。我更喜欢将扩展方法按它们适用于的类别/结构/枚举进行分组。


63

将一个枚举类型先转换为int类型,然后再转换为另一个枚举类型(假设你希望根据值进行映射):

只需将一个枚举类型先转换为int类型,再将其转换为另一个枚举类型即可(假设您希望基于值进行映射):

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

5
尽管很少见到这种情况,而且性别方面几乎不可能出现这种情况,但可能存在一些枚举是由long(或ulong)支持的,而不是由int支持的,其成员定义高于int.MaxValue(或低于int.MinValue),在这种情况下进行int强制转换可能会溢出,导致未定义的枚举值。 - Rich O'Kelly
当然,正确的方式应该是(Gender2)((insert underlying type here)gender1),但我认为上面的例子已经传达了正确的思想,所以我不会改变它。 - Adrian Zanescu
3
需要两个枚举类型具有相同的值并且顺序也相同。虽然这解决了特定的问题,但这非常脆弱,我不建议在一般的枚举映射中使用此方法。 - sonicblis
5
好的.... 当然需要根据某些东西来完成映射。在这种情况下,映射是基于整数值的。如果要根据名称进行映射,需要不同的代码。对于其他类型的映射,则需要其他内容。没有人说这是“针对枚举映射的通用方法”,除非您可以说明“通用映射”具体指什么。 - Adrian Zanescu

27

如果我们有:

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

24

为了更加彻底,我通常会创建一对函数。一个接收枚举1并返回枚举2,另一个接收枚举2并返回枚举1。每个函数都由一个case语句组成,将输入映射到输出。如果默认情况下出现意外的值,则抛出异常并显示相关信息。

在这种特定情况下,您可以利用Male和Female的整数值相同这一事实,但我建议避免使用这种方法,因为它是hackish的。而且如果任何一个枚举类型在未来发生更改,此方法就会失效。


8
+1 我见过很多开发者屈服于使用枚举的整数值进行转换的冲动,但这种方法非常容易出错。编写两个函数的老派方法已经证明其价值经久不衰... - Hemant

16
我写了这篇答案,因为我认为大多数已提供的答案存在根本性问题,而可接受的答案也是不完整的。
按枚举整数值进行映射
这种方法很糟糕,因为它假定MyGender和TheirGender的整数值始终可比较。在实践中,即使在单个项目中,您也很少能够保证这一点,更不用说单独的服务了。
我们采取的方法应该是可以用于其他枚举映射情况的方法。我们永远不应该假设一个枚举与另一个枚举完全相关 - 尤其是当我们可能无法控制其中一个时。
按枚举字符串值进行映射
这样做会好一些,因为即使整数表示发生变化,MyGender.Male仍将转换为TheirGender.Male,但仍不理想。
我不建议采用这种方法,因为它假设名称值不会更改,并且始终保持相同。考虑到未来的增强,您无法保证这种情况; 考虑如果添加了MyGender.NotKnown。您可能希望将其映射到TheirGender.Unknown,但不支持此操作。
此外,在某些上下文中,假设一个枚举等同于另一个枚举是通常不好的。如前所述,理想的方法适用于其他枚举映射要求。
显式映射枚举
这种方法使用switch语句将MyGender明确映射到TheirGender。
这很好,因为:
涵盖底层整数值更改的情况。
涵盖枚举名称更改的情况(即没有假设 - 开发人员需要更新代码以处理该场景 - 很好)。
处理无法映射枚举值的情况。
处理添加新枚举值并且默认情况下无法映射的情况(再次,没有假设 - 很好)。
可以轻松更新以支持MyGender或TheirGender的新枚举值。
对于所有枚举映射要求都可以采取相同的方法。
假设我们有以下枚举:
public enum MyGender
{
    Male = 0,
    Female = 1,
}

public enum TheirGender
{
    Male = 0,
    Female = 1,
    Unknown = 2,
}

我们可以创建以下函数来将“他们的枚举转换为我的”:
public MyGender GetMyGender(TheirGender theirGender)
{
    switch (theirGender)
    {
        case TheirGender.Male:
            return MyGender.Male;

        case TheirGender.Female:
            return MyGender.Female;

        default:
            throw new InvalidEnumArgumentException(nameof(theirGender), (int)theirGender, typeof(TheirGender));
    }
}

之前的回答建议返回可空枚举类型(TheirGender?),对于任何无法匹配的输入返回null。这是不好的,null并不等同于未知映射。如果无法映射输入,则应抛出异常,否则方法应该被命名得更加明确以反映其行为:
public TheirGender? GetTheirGenderOrDefault(MyGender myGender)
{
    switch (myGender)
    {
        case MyGender.Male:
            return TheirGender.Male;
            
        case MyGender.Female:
            return TheirGender.Female;
            
        default:
            return default(TheirGender?);
    }
}

额外考虑事项

如果有可能在解决方案的各个部分需要多次使用此方法,可以考虑创建一个扩展方法:

public static class TheirGenderExtensions
{
    public static MyGender GetMyGender(this TheirGender theirGender)
    {
        switch (theirGender)
        {
            case TheirGender.Male:
                return MyGender.Male;

            case TheirGender.Female:
                return MyGender.Female;

            default:
                throw new InvalidEnumArgumentException(nameof(theirGender), (int)theirGender, typeof(TheirGender));
        }
    }
}

如果您正在使用C#8,您可以使用switch表达式的语法表达式主体来简化代码:
public static class TheirGenderExtensions
{
    public static MyGender GetMyGender(this TheirGender theirGender)
        => theirGender switch
        {
            TheirGender.Male => MyGender.Male,
            TheirGender.Female => MyGender.Female,
            _ => throw new InvalidEnumArgumentException(nameof(theirGender), (int)theirGender, typeof(TheirGender))
        };
}

如果您只需要映射单个类中的枚举,则扩展方法可能过于繁琐。在这种情况下,该方法可以在类内部声明。
此外,如果映射仅在单个方法中进行,则可以将其声明为本地函数
public static void Main()
{
    Console.WriteLine(GetMyGender(TheirGender.Male));
    Console.WriteLine(GetMyGender(TheirGender.Female));
    Console.WriteLine(GetMyGender(TheirGender.Unknown));
    
    static MyGender GetMyGender(TheirGender theirGender)
        => theirGender switch
        {
            TheirGender.Male => MyGender.Male,
            TheirGender.Female => MyGender.Female,
            _ => throw new InvalidEnumArgumentException(nameof(theirGender), (int)theirGender, typeof(TheirGender))
        };
}

这里是一个包含上述示例的dotnet fiddle链接

简而言之:

不要:

  • 通过整数值映射枚举
  • 通过名称映射枚举

要:

  • 使用switch语句显式映射枚举
  • 当无法映射值时抛出异常,而不是返回null
  • 考虑使用扩展方法

1
太棒了!我们正在将第三方枚举映射到我们自己的系统无关映射中。我再次强调第三方API会发生变化(可以、会、并且一定会发生变化!)你希望每次更改都是一个难以追踪的错误(请参见此答案对InvalidEnumArgumentException的使用)。我想不出有哪种情况下,如果我们按照整数映射在枚举上正常操作,我们不会受到更大的伤害。一个名称的模糊含义足以改变预期的系统行为。第三方系统有时候真的很麻烦... 简而言之,当没有精确匹配时,请抛出错误并唤醒开发人员。 - Hunter-Orionnoir

15
你可以编写一个简单的通用扩展方法,如下所示。
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

9
你可以编写一个简单的函数,如下所示:
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)。 - Justin

4
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接