Can we define implicit conversions of enums in c#?

前端 未结 13 2357
孤街浪徒
孤街浪徒 2020-11-30 18:37

Is it possible to define an implicit conversion of enums in c#?

something that could achieve this?

public enum MyEnum
{
    one = 1, two = 2
}

MyEnu         


        
相关标签:
13条回答
  • 2020-11-30 18:49

    You probably could, but not for the enum (you can't add a method to it). You could add an implicit conversion to you own class to allow an enum to be converted to it,

    public class MyClass {
    
        public static implicit operator MyClass ( MyEnum input ) {
            //...
        }
    }
    
    MyClass m = MyEnum.One;
    

    The question would be why?

    In general .Net avoids (and you should too) any implicit conversion where data can be lost.

    0 讨论(0)
  • 2020-11-30 18:58

    I found even easier solution taken from here https://codereview.stackexchange.com/questions/7566/enum-vs-int-wrapper-struct I pasted the code below from that link just in case it does not work in the future.

    struct Day
    {
        readonly int day;
    
        public static readonly Day Monday = 0;
        public static readonly Day Tuesday = 1;
        public static readonly Day Wednesday = 2;
        public static readonly Day Thursday = 3;
        public static readonly Day Friday = 4;
        public static readonly Day Saturday = 5;
        public static readonly Day Sunday = 6;
    
        private Day(int day)
        {
            this.day = day;
        }
    
        public static implicit operator int(Day value)
        {
            return value.day;
        }
    
        public static implicit operator Day(int value)
        {
            return new Day(value);
        }
    }
    
    0 讨论(0)
  • 2020-11-30 19:02

    There is a solution. Consider the following:

    public sealed class AccountStatus
    {
        public static readonly AccountStatus Open = new AccountStatus(1);
        public static readonly AccountStatus Closed = new AccountStatus(2);
    
        public static readonly SortedList<byte, AccountStatus> Values = new SortedList<byte, AccountStatus>();
        private readonly byte Value;
    
        private AccountStatus(byte value)
        {
            this.Value = value;
            Values.Add(value, this);
        }
    
    
        public static implicit operator AccountStatus(byte value)
        {
            return Values[value];
        }
    
        public static implicit operator byte(AccountStatus value)
        {
            return value.Value;
        }
    }
    

    The above offers implicit conversion:

            AccountStatus openedAccount = 1;            // Works
            byte openedValue = AccountStatus.Open;      // Works
    

    This is a fair bit more work than declaring a normal enum (though you can refactor some of the above into a common generic base class). You can go even further by having the base class implement IComparable & IEquatable, as well as adding methods to return the value of DescriptionAttributes, declared names, etc, etc.

    I wrote a base class (RichEnum<>) to handle most fo the grunt work, which eases the above declaration of enums down to:

    public sealed class AccountStatus : RichEnum<byte, AccountStatus>
    {
        public static readonly AccountStatus Open = new AccountStatus(1);
        public static readonly AccountStatus Closed = new AccountStatus(2);
    
        private AccountStatus(byte value) : base (value)
        {
        }
    
        public static implicit operator AccountStatus(byte value)
        {
            return Convert(value);
        }
    }
    

    The base class (RichEnum) is listed below.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Resources;
    
    namespace Ethica
    {
        using Reflection;
        using Text;
    
        [DebuggerDisplay("{Value} ({Name})")]
        public abstract class RichEnum<TValue, TDerived>
                    : IEquatable<TDerived>,
                      IComparable<TDerived>,
                      IComparable, IComparer<TDerived>
            where TValue : struct , IComparable<TValue>, IEquatable<TValue>
            where TDerived : RichEnum<TValue, TDerived>
        {
            #region Backing Fields
    
            /// <summary>
            /// The value of the enum item
            /// </summary>
            public readonly TValue Value;
    
            /// <summary>
            /// The public field name, determined from reflection
            /// </summary>
            private string _name;
    
            /// <summary>
            /// The DescriptionAttribute, if any, linked to the declaring field
            /// </summary>
            private DescriptionAttribute _descriptionAttribute;
    
            /// <summary>
            /// Reverse lookup to convert values back to local instances
            /// </summary>
            private static SortedList<TValue, TDerived> _values;
    
            private static bool _isInitialized;
    
    
            #endregion
    
            #region Constructors
    
            protected RichEnum(TValue value)
            {
                if (_values == null)
                    _values = new SortedList<TValue, TDerived>();
                this.Value = value;
                _values.Add(value, (TDerived)this);
            }
    
            #endregion
    
            #region Properties
    
            public string Name
            {
                get
                {
                    CheckInitialized();
                    return _name;
                }
            }
    
            public string Description
            {
                get
                {
                    CheckInitialized();
    
                    if (_descriptionAttribute != null)
                        return _descriptionAttribute.Description;
    
                    return _name;
                }
            }
    
            #endregion
    
            #region Initialization
    
            private static void CheckInitialized()
            {
                if (!_isInitialized)
                {
                    ResourceManager _resources = new ResourceManager(typeof(TDerived).Name, typeof(TDerived).Assembly);
    
                    var fields = typeof(TDerived)
                                    .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                                    .Where(t => t.FieldType == typeof(TDerived));
    
                    foreach (var field in fields)
                    {
    
                        TDerived instance = (TDerived)field.GetValue(null);
                        instance._name = field.Name;
                        instance._descriptionAttribute = field.GetAttribute<DescriptionAttribute>();
    
                        var displayName = field.Name.ToPhrase();
                    }
                    _isInitialized = true;
                }
            }
    
            #endregion
    
            #region Conversion and Equality
    
            public static TDerived Convert(TValue value)
            {
                return _values[value];
            }
    
            public static bool TryConvert(TValue value, out TDerived result)
            {
                return _values.TryGetValue(value, out result);
            }
    
            public static implicit operator TValue(RichEnum<TValue, TDerived> value)
            {
                return value.Value;
            }
    
            public static implicit operator RichEnum<TValue, TDerived>(TValue value)
            {
                return _values[value];
            }
    
            public static implicit operator TDerived(RichEnum<TValue, TDerived> value)
            {
                return value;
            }
    
            public override string ToString()
            {
                return _name;
            }
    
            #endregion
    
            #region IEquatable<TDerived> Members
    
            public override bool Equals(object obj)
            {
                if (obj != null)
                {
                    if (obj is TValue)
                        return Value.Equals((TValue)obj);
    
                    if (obj is TDerived)
                        return Value.Equals(((TDerived)obj).Value);
                }
                return false;
            }
    
            bool IEquatable<TDerived>.Equals(TDerived other)
            {
                return Value.Equals(other.Value);
            }
    
    
            public override int GetHashCode()
            {
                return Value.GetHashCode();
            }
    
            #endregion
    
            #region IComparable Members
    
            int IComparable<TDerived>.CompareTo(TDerived other)
            {
                return Value.CompareTo(other.Value);
            }
    
            int IComparable.CompareTo(object obj)
            {
                if (obj != null)
                {
                    if (obj is TValue)
                        return Value.CompareTo((TValue)obj);
    
                    if (obj is TDerived)
                        return Value.CompareTo(((TDerived)obj).Value);
                }
                return -1;
            }
    
            int IComparer<TDerived>.Compare(TDerived x, TDerived y)
            {
                return (x == null) ? -1 :
                       (y == null) ? 1 :
                        x.Value.CompareTo(y.Value);
            }
    
            #endregion
    
            public static IEnumerable<TDerived> Values
            {
                get
                {
                    return _values.Values;
                }
            }
    
            public static TDerived Parse(string name)
            {
                foreach (TDerived value in _values.Values)
                    if (0 == string.Compare(value.Name, name, true) || 0 == string.Compare(value.DisplayName, name, true))
                        return value;
    
                return null;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 19:02

    I've worked around an issue with sehe's answer when running the code on MS .net (non-Mono). For me specifically the issue occurred on .net 4.5.1 but other versions seem affected, too.

    The issue

    accessing a public static TDervied MyEnumValue by reflection (via FieldInfo.GetValue(null) does not initialize said field.

    The workaround

    Instead of assigning names to TDerived instances upon the static initializer of RichEnum<TValue, TDerived> this is done lazily on first access of TDerived.Name. The code:

    public abstract class RichEnum<TValue, TDerived> : EquatableBase<TDerived>
        where TValue : struct, IComparable<TValue>, IEquatable<TValue>
        where TDerived : RichEnum<TValue, TDerived>
    {
        // Enforcing that the field Name (´SomeEnum.SomeEnumValue´) is the same as its 
        // instances ´SomeEnum.Name´ is done by the static initializer of this class.
        // Explanation of initialization sequence:
        // 1. the static initializer of ´RichEnum<TValue, TDerived>´ reflects TDervied and 
        //    creates a list of all ´public static TDervied´ fields:
        //   ´EnumInstanceToNameMapping´
        // 2. the static initializer of ´TDerive´d assigns values to these fields
        // 3. The user is now able to access the values of a field.
        //    Upon first access of ´TDervied.Name´ we search the list 
        //    ´EnumInstanceToNameMapping´ (created at step 1) for the field that holds
        //    ´this´ instance of ´TDerived´.
        //    We then get the Name for ´this´ from the FieldInfo
        private static readonly IReadOnlyCollection<EnumInstanceReflectionInfo> 
                                EnumInstanceToNameMapping = 
            typeof(TDerived)
                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                .Where(t => t.FieldType == typeof(TDerived))
                .Select(fieldInfo => new EnumInstanceReflectionInfo(fieldInfo))
                .ToList();
    
        private static readonly SortedList<TValue, TDerived> Values =
            new SortedList<TValue, TDerived>();
    
        public readonly TValue Value;
    
        private readonly Lazy<string> _name;
    
        protected RichEnum(TValue value)
        {
            Value = value;
    
            // SortedList doesn't allow duplicates so we don't need to do
            // duplicate checking ourselves
            Values.Add(value, (TDerived)this);
    
            _name = new Lazy<string>(
                        () => EnumInstanceToNameMapping
                             .First(x => ReferenceEquals(this, x.Instance))
                             .Name);
        }
    
        public string Name
        {
            get { return _name.Value; }
        }
    
        public static implicit operator TValue(RichEnum<TValue, TDerived> richEnum)
        {
            return richEnum.Value;
        }
    
        public static TDerived Convert(TValue value)
        {
            return Values[value];
        }
    
        protected override bool Equals(TDerived other)
        {
            return Value.Equals(other.Value);
        }
    
        protected override int ComputeHashCode()
        {
            return Value.GetHashCode();
        }
    
        private class EnumInstanceReflectionInfo
        {
            private readonly FieldInfo _field;
            private readonly Lazy<TDerived> _instance;
    
            public EnumInstanceReflectionInfo(FieldInfo field)
            {
                _field = field;
                _instance = new Lazy<TDerived>(() => (TDerived)field.GetValue(null));
            }
    
            public TDerived Instance
            {
                get { return _instance.Value; }
            }
    
            public string Name { get { return _field.Name; } }
        }
    }
    

    which - in my case - is based upon EquatableBase<T>:

    public abstract class EquatableBase<T>
        where T : class 
    {
        public override bool Equals(object obj)
        {
            if (this == obj)
            {
                return true;
            }
    
            T other = obj as T;
            if (other == null)
            {
                return false;
            }
    
            return Equals(other);
        }
    
        protected abstract bool Equals(T other);
    
        public override int GetHashCode()
        {
            unchecked
            {
                return ComputeHashCode();
            }
        }
    
        protected abstract int ComputeHashCode();
    }
    

    Note

    The above code does not incorporate all features of Mark's original answer!

    Thanks

    Thanks to Mark for providing his RichEnum implementation and thanks to sehe for providing some improvements!

    0 讨论(0)
  • 2020-11-30 19:04

    Introducing implicit conversions for enum types would break type safety, so I'd not recommend to do that. Why would you want to do that? The only use case for this I've seen is when you want to put the enum values into a structure with a pre-defined layout. But even then, you can use the enum type in the structure and just tell the Marshaller what he should do with this.

    0 讨论(0)
  • 2020-11-30 19:05

    You cannot declare implicit conversions on enum types, because they can't define methods. The C# implicit keyword compiles into a method starting with 'op_', and it wouldn't work in this case.

    0 讨论(0)
提交回复
热议问题