将枚举转换为其他类型的枚举

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

我的问题是,我怎样才能快速而漂亮地将它们的枚举转换成我的枚举呢?

197437 次浏览

为了全面起见,我通常创建两个函数,一个接受 Enum1并返回 Enum2,另一个接受 Enum2并返回 Enum1。每个例子都包含一个 case 语句,该 case 语句将输入映射到输出,并且引发一个异常,其中包含一条抱怨意外值的消息。

在这种特殊情况下,您可以利用这样一个事实,即错误的整数值是相同的,但是我会避免这种情况,因为它是骇客式的,而且如果枚举在未来发生变化,它会受到破坏。

给定 Enum1 value = ...,那么如果你指的是名字:

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

如果指的是数值,那么通常可以直接进行强制转换:

Enum2 value2 = (Enum2)value;

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

可以使用 ToString ()将第一个枚举转换为它的名称,然后再转换为 Enum。Parse ()将字符串转换回另一个 Enum。如果目标枚举不支持该值(即“ Unknown”值) ,则引发异常

你可以编写如下简单的函数:

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

当使用 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
}
}

显然,如果您不想使用单独的类,那么就没有必要使用单独的类。我倾向于将扩展方法按照它们应用到的类/结构/枚举进行分组。

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

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

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

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());
}

不久前,我编写了一个集合扩展方法,可用于几种不同类型的 Enum。其中一个特别适用于您试图完成的任务,并使用 FlagsAttribute处理 Enum,以及使用不同底层类型处理 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);
}

这一个将改变类型的 Enum就像你正在尝试做的。

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

但是要注意,您可以使用这种方法在任何 Enum和任何其他 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。

混合 EnumFlagsAttribute和那些没有 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.GrapeLetters的转换将产生 Letters.C,这似乎不直观。

如果有人感兴趣,这里有一个扩展方法版本

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


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

如果有:

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;

如果你想覆盖在’=’符号右边的枚举比左边的枚举有更多的值的情况-你必须编写你自己的方法/字典来覆盖这种情况,就像其他人建议的那样。

我知道这是一个老问题,而且有很多答案,但是我发现在公认的答案中使用 switch 语句有点麻烦,所以我的建议是:

我个人最喜欢的方法是使用 dictionary,其中关键字是源枚举,值是目标枚举——因此,在提出这个问题的情况下,我的代码应该是这样的:

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


}

基于上面的 贾斯汀的答案,我想到了这个:

    /// <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
);
}
}
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;
}

如果枚举成员具有不同的值,您可以应用如下内容:

public static MyGender? MapToMyGender(this Gender gender)
{
return gender switch
{
Gender.Male => MyGender.Male,
Gender.Female => MyGender.Female,
Gender.Unknown => null,
_ => throw new InvalidEnumArgumentException($"Invalid gender: {gender}")
};
}

然后你可以打电话: var myGender = gender.MapToMyGender();

更新: 上面的代码只适用于 C # 8。 对于旧版本的 C # ,您可以使用 switch 语句而不是 switch 表达式:

public static MyGender? MapToMyGender(this Gender gender)
{
switch (gender)
{
case Gender.Male:
return MyGender.Male;
case Gender.Female:
return MyGender.Female;
case Gender.Unknown:
return null;
default:
throw new InvalidEnumArgumentException($"Invalid gender: {gender}")
};
}

我写这个答案是因为我相信大多数已经提供的答案都存在根本性的问题,而那些可以接受的答案是不完整的。

通过枚举整数值进行映射

这种方法很糟糕,因为它假设 MyGenderTheirGender的整数值始终保持可比性。在实践中,即使在单个项目中也很少能够保证这一点,更不用说单独的服务了。

我们采用的方法应该可以用于其他枚举映射情况。我们永远不应该假设一个枚举与另一个枚举具有完全相同的关系——特别是当我们可能无法控制这个或那个枚举时。

通过枚举字符串值映射

这稍微好一点,因为即使整数表示形式发生了变化,MyGender.Male仍然会转换为 TheirGender.Male,但仍然不是理想的。

我不赞成这种方法,因为它假定名称值不会改变,而且始终保持不变。考虑到未来的增强,您不能保证情况会是这样; 请考虑是否添加了 MyGender.NotKnown。您可能希望将此映射到 TheirGender.Unknown,但是这不受支持。

此外,假设一个枚举等同于另一个枚举的名称通常是不好的,因为在某些上下文中可能不是这种情况。如前所述,理想的方法可以用于其他枚举映射需求。

显式映射枚举

这种方法使用 switch 语句显式地将 MyGender映射到 TheirGender

最好是这样:

  • 覆盖基础整数值更改的情况。
  • 涵盖枚举名称改变的情况(即不做任何假设-开发人员需要更新代码来处理场景-很好)。
  • 处理无法映射枚举值的情况。
  • 处理添加了新枚举值并且无法在默认情况下映射的情况(同样,没有做出任何假设-很好)。
  • 可以很容易地更新以支持 MyGenderTheirGender的新枚举值。
  • 对于所有枚举映射需求都可以采用相同的方法。

假设我们有以下枚举:

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,可以使用 开关表达式的语法表达体来整理代码:

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
  • 考虑使用扩展方法

你可以做到的

public enum TestNum {
{
[EnumConvert( NewType.AType )]
Test1,


[EnumConvert( NewType.BType )]
Test2
}


public enum NewType {
AType,
BType
}


public static class EnumExtensions {
public static Enum GetEnum(this Enum value ) {
var attribute = (EnumConvertAttribute)value.GetType()
.GetField( value.ToString() )
.GetCustomAttribute( false )
.Where( a => a is EnumConvertAttribute
.FirstOrDefault();


if( attribute == null ) {
throw new ArgumentNullException();
}


try {
return attribute.TargetEnum;
} catch ( Exception ex ) {
throw new InvalidArgumentException();
}
}

}

公共类 EnumConvertAttribute: Attribute { 公共枚举;

public EnumConvertAttribute( object e ) {
TargetEnum = (Enum)e;
}

}

电话: Var t1 = TestNum. Test2; Var t2 = t1.GetEnum () ;

T2 = NewType.BType;

如果你不介意拳击的话,还是有用的。大量转换枚举并不是一个好的实践。你确实得到了强类型,你没有两个转换方法,最终在维护时创建一个 bug,你不祈祷字符串或整型比较不会失败。

Enum1 var1;
Enum2 var2;


var2 = Enum2.fromValue(var1.getValue())


public enum Enum2 {
FEMALE("FEMALE"),
MALE("MALE")
private String value;


public static Enum2 fromValue(String value) {
for (Enum2 b : Enum2.values()) {
if (b.value.equals(value)) {
return b;
}
}
throw new IllegalArgumentException("Unexpected value '" + value + "'");
}
}