繁体   English   中英

将枚举转换为另一种类型的枚举

[英]convert an enum to another type of enum

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

我的问题是我怎样才能写一些快速而好的东西来从他们的枚举转换为我的?

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

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

如果你的意思是数值,你通常可以只转换:

Enum2 value2 = (Enum2)value;

(不过,对于演员表,您可能希望使用Enum.IsDefined来检查有效值)

当使用 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);

如果我们有:

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;

如果您想涵盖“=”符号右侧的枚举比左侧的枚举具有更多值的情况 - 您将必须编写自己的方法/字典来涵盖其他人建议的内容。

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

在这种特殊情况下,您可以利用男性和女性的整数值相同的事实,但我会避免这种情况,因为如果将来任何一个枚举发生变化,它都是黑客行为并且可能会损坏。

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

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

你可以写一个简单的函数,如下所示:

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

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

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

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

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

按枚举整数值映射

这种方法很糟糕,因为它假设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,您可以使用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 链接

tl;博士:

不要:

  • 按整数值映射枚举
  • 按名称映射枚举

做:

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

根据上面贾斯汀的回答,我想出了这个:

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

不久前我写了一组扩展方法,适用于几种不同类型的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之间进行转换,即使是那些没有标志的。 例如:

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未定义的Enum值。 例如:

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

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

EnumFlagsAttribute和那些没有它的混合的另一个副作用是转换过程不会导致它们的值之间 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>();
}

在这种情况下,由于Flavors.Peach的支持值为 8,因此letters的值将是Letters.H而不是Letters.D 。此外,来自Flavors.Cherry | Flavors.Grape的转换Flavors.Cherry | Flavors.Grape Flavors.Cherry | Flavors.Grape to Letters会产生Letters.C ,这看起来不直观。

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

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

我知道这是一个老问题并且有很多答案,但是我发现在接受的答案中使用 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;
    }

}

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

对于笑容,你可以这样做

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

}

公共class EnumConvertAttribute:属性{公共枚举目标枚举;

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

}

调用:var t1 = TestNum.Test2; var t2 = t1.GetEnum();

t2 = 新类型.BType;

它有效,假设你不介意拳击。 无论如何,大量转换枚举并不是一个好习惯。 您确实得到了强类型,在维护时没有两种转换方法最终会产生错误,并且您不祈祷字符串或 int 比较不会失败。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM