【问题标题】:convert an enum to another type of enum将枚举转换为另一种类型的枚举
【发布时间】:2010-12-21 13:07:56
【问题描述】:

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

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

【问题讨论】:

  • 你想把“未知”转换成什么?
  • 当两者具有相同的值时,您可以将枚举类型转换为其他枚举类型,请参阅ideone.com/7lgvgf
  • 如果这只能在 Java 中工作......

标签: c# enums


【解决方案1】:

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

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

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

Enum2 value2 = (Enum2)value;

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

【讨论】:

  • 这是更好的答案
  • 这是一个使用Enum.Tryparse的版本:Enum2 value2 = Enum.TryParse(value.ToString(), out Enum2 outValue) ? outValue : Enum2.Unknown; 这将允许您处理Enum2中不存在的输入值,而无需调用Enum.IsDefined或捕获ArgumentExceptionEnum.Parse 抛出。请注意,参数的顺序或多或少与Enum.Parse相反。
  • 是的,这应该是 IMO 接受的答案
【解决方案2】:

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

显然,如果您不想使用单独的类,则无需使用。我的偏好是保持扩展方法按它们适用的类/结构/枚举进行分组。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 虽然不太可能在“野外”看到它,而且性别也不太可能出现这种情况,但可能存在一些由long(或ulong)支持的枚举) 而不是int 定义的成员高于int.MaxValue(或低于int.MinValue),在这种情况下,转换为int 可能会溢出,你最终会得到一个未定义的枚举值,它应该是已定义。
    • 当然。正确的方法是 (Gender2)((在此处插入基础类型)gender1) 但我认为上面的示例给出了正确的想法,所以我不会改变它。
    • 这要求两个枚举以相同的顺序具有相同的值。虽然它解决了这个特定问题,但这真的很脆弱,我一般不会将它用于枚举映射。
    • 好吧....呃! .映射需要基于某些东西来完成。在这种情况下,映射是整数值。对于基于名称的映射,您需要不同的代码。对于另一种映射其他的东西。没有人说这是“用于一般的枚举映射”,除非您可以尝试指定“一般映射”的含义,否则这种情况不存在
    【解决方案4】:

    如果我们有:

    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;
    

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

    【讨论】:

    • 你的回答就像是在问一个问题!?如果是,这不是答案,如果不是,则有 similar answer above ;)。
    【解决方案5】:

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

    在这种特殊情况下,您可以利用 Male 和 Female 的整数值相同的事实,但我会避免这种情况,因为如果将来任何一个枚举发生变化,它会很老套并且容易损坏。

    【讨论】:

    • +1 我看到许多开发人员放弃了使用枚举的整数值来转换它们的冲动,但这很容易出错。随着时间的推移,编写 2 个函数的老派方法已经证明了它的价值……
    【解决方案6】:

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

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

    【讨论】:

    • 它不包括上述答案中建议的缺失值的情况。你也应该修改这个覆盖这种情况的扩展方法。
    【解决方案7】:

    你可以写一个像下面这样的简单函数:

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

    【讨论】:

    • 这不是函数。预期“MyGender”,而您返回“void”
    【解决方案8】:

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

    public static TEnum ConvertEnum<TEnum >(this Enum source)
        {
            return (TEnum)Enum.Parse(typeof(TEnum), source.ToString(), true);
        }
    
    // Usage
    NewEnumType newEnum = oldEnumVar.ConvertEnum<NewEnumType>();
    

    【讨论】:

    • 这不是意味着两个枚举具有相同的数值吗?
    • 不,这是按名称按字符串转换。所以 Enum.Foo (1) 将转换为 Enum2.Foo (2) 即使它们的数值不同。
    【解决方案9】:

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

    按枚举整数值映射

    这种方法不好,因为它假定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,你可以使用 syntax for switch expressionsexpression bodies 来整理代码:

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

    如果您只在一个类中映射枚举,那么扩展方法可能会过大。在这种情况下,可以在类本身中声明该方法。

    此外,如果映射只发生在单个方法中,那么您可以将其声明为local function

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

    Here's a dotnet fiddle link with the above example.

    tl;博士:

    不要:

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

    做:

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

    【讨论】:

      【解决方案10】:

      根据上面的Justin's 回答我想出了这个:

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

      【讨论】:

        【解决方案11】:
        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;
        }
        

        【讨论】:

          【解决方案12】:

          不久前我写了一套扩展方法,适用于几种不同的Enums。一个特别适用于您要完成的工作,并使用 FlagsAttribute 以及具有不同基础类型的 Enums 处理 Enums。

          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 混合使用的另一个副作用是转换过程不会导致它们的值之间存在一对一的匹配。

          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 的转换将产生 @ 987654349@,这看起来不直观。

          【讨论】:

            【解决方案13】:

            如果枚举成员有不同的值,你可以应用这样的东西:

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

            【讨论】:

            • 欢迎来到 StackOverflow。此代码仅适用于 C# 8。请在您的答案中突出显示此信息。
            【解决方案14】:

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

            【讨论】:

            • 我喜欢这种方法,因为您也可以枚举两个枚举以填充字典。 (当然,当它们的顺序相同时)
            【解决方案15】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2023-02-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-10-11
              相关资源
              最近更新 更多