convert an enum to another type of enum

前端 未结 15 1548
感动是毒
感动是毒 2020-11-30 02:36

I have an enum of for example \'Gender\' (Male =0 , Female =1) and I have another enum from a service which has its own Gender enum (Male =0

相关标签:
15条回答
  • 2020-11-30 03:08

    If we have:

    enum Gender
    {
        M = 0,
        F = 1,
        U = 2
    }
    

    and

    enum Gender2
    {
        Male = 0,
        Female = 1,
        Unknown = 2
    }
    

    We can safely do

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

    Or even

    var enumOfGender2Type = (Gender2)0;
    

    If you want to cover the case where an enum on the right side of the '=' sign has more values than the enum on the left side - you will have to write your own method/dictionary to cover that as others suggested.

    0 讨论(0)
  • 2020-11-30 03:08

    you could write a simple function like the following:

    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
        }
    }
    
    0 讨论(0)
  • 2020-11-30 03:08

    I know that's an old question and have a lot of answers, However I find that using a switch statement as in the accepted answer is somewhat cumbersome, so here are my 2 cents:

    My personal favorite method is to use a dictionary, where the key is the source enum and the value is the target enum - so in the case presented on the question my code would look like this:

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

    Of course, this can be wrapped in a static class and be used as an extension methods:

    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;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-30 03:10

    Based on Justin's answer above I came up with this:

        /// <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
                );
            }
        }
    
    0 讨论(0)
  • 2020-11-30 03:13

    I wrote a set extension methods a while back that work for several different kinds of Enums. One in particular works for what you are trying to accomplish and handles Enums with the FlagsAttribute as well as Enums with different underlying types.

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

    From there you can add other more specific extension methods.

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

    This one will change types of Enums like you are trying to do.

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

    Be warned, though, that you CAN convert between any Enum and any other Enum using this method, even those that do not have flags. For example:

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

    The variable turtle will have a value of Turtle.Blue.

    However, there is safety from undefined Enum values using this method. For instance:

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

    In this case, access will be set to WriteAccess.ReadWrite, since the WriteAccess Enum has a maximum value of 3.

    Another side effect of mixing Enums with the FlagsAttribute and those without it is that the conversion process will not result in a 1 to 1 match between their values.

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

    In this case, letters will have a value of Letters.H instead of Letters.D, since the backing value of Flavors.Peach is 8. Also, a conversion from Flavors.Cherry | Flavors.Grape to Letters would yield Letters.C, which can seem unintuitive.

    0 讨论(0)
  • 2020-11-30 03:17

    I wrote this answer because I believe there are fundamental issues with the majority of answers already provided, and the ones that are acceptable are incomplete.

    Mapping by enum integer value

    This approach is bad simply because it assumes that the integer values of both MyGender and TheirGender will always remain comparable. In practice, it is very rare that you can guarantee this even within a single project, let alone a separate service.

    The approach we take should be something that can be used for other enum-mapping cases. We should never assume that one enum identically relates to another - especially when we may not have control over one or another.

    Mapping by enum string value

    This is a little better, as MyGender.Male will still convert to TheirGender.Male even if the integer representation is changed, but still not ideal.

    I would discourage this approach as it assumes the name values will not change, and will always remain identical. Considering future enhancements, you cannot guarantee that this will be the case; consider if MyGender.NotKnown was added. It is likely that you would want this to map to TheirGender.Unknown, but this would not be supported.

    Also, it is generally bad to assume that one enum equates to another by name, as this might not be the case in some contexts. As mentioned earlier, an ideal approach would work for other enum-mapping requirements.

    Explicitly mapping enums

    This approach explictly maps MyGender to TheirGender using a switch statement.

    This is better as:

    • Covers the case where the underlying integer value changes.
    • Covers the case where the enum names changes (i.e. no assumptions - the developer will need to update the code to handle the scenario - good).
    • Handles cases where enum values cannot be mapped.
    • Handles cases where new enum values are added and cannot be mapped by default (again, no assumptions made - good).
    • Can easily be updated to support new enum values for either MyGender or TheirGender.
    • The same approach can be taken for all enum mapping requirements.

    Assuming we have the following enums:

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

    We can create the following function to "convert from their enum to mine":

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

    A previous answer suggested returning a nullable enum (TheirGender?) and returning null for any unmatched input. This is bad; null is not the same as an unknown mapping. If the input cannot be mapped, an exception should be thrown, else the method should be named more explictly to the behaviour:

    public TheirGender? GetTheirGenderOrDefault(MyGender myGender)
    {
        switch (myGender)
        {
            case MyGender.Male:
                return TheirGender.Male;
                
            case MyGender.Female:
                return TheirGender.Female;
                
            default:
                return default(TheirGender?);
        }
    }
    

    Additional considerations

    If it is likely that this method will be required more than once in various parts of the solution, you could consider creating an extension method for this:

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

    If you are using C#8, you can use the syntax for switch expressions and expression bodies to neaten up the code:

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

    If you will only ever be mapping the enums within a single class, then an extension method may be overkill. In this case, the method can be declared within the class itself.

    Furthermore, if the mapping will only ever take place within a single method, then you can declare this as a 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;dr:

    Do not:

    • Map enums by integer value
    • Map enums by name

    Do:

    • Map enums explicitly using a switch statement
    • Throw an exception when a value cannot be mapped rather than returning null
    • Consider using extension methods
    0 讨论(0)
提交回复
热议问题