Can we define implicit conversions of enums in c#?
Asked Answered
R

15

153

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

something that could achieve this?

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

MyEnum number = MyEnum.one;
long i = number;

If not, why not?

Roane answered 4/11, 2008 at 12:6 Comment(3)
I'd like to do this too. We have an enum enum YesNo {Yes, No} that could implicitly convert to bool.Doak
Noting that this concept disables compiler type safety checking. Longer term, an explicit conversion shorthand like a trailing '~' might be better.Afire
The link is no longer valid - can we either remove the link, or repost the website somewhere?Mcafee
G
138

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;
        }
    }
}
Gautea answered 1/6, 2010 at 11:31 Comment(12)
Corrected a little offguard error in the post :-) It's public static implicit operator AccountStatus(byte value) { return Convert(value); } NOT return Convert(byte);Haywoodhayyim
I made this baseclass compile. Do you mind if I edit in the changes?Ogham
In you first example (non-base class approach) the SortedList needs to be defined before (above in code) the AccountStatus instances, otherwise the constructor will complain during runtime that the SortedList is nullFalcongentle
I thought you couldn't capture this() in the constructor... I believe this should throw a type initializer error, no?Lorettalorette
This solution might be 'right' as an exercise, or testing someone's programming skills but, please, don't do this in real life. Not only it's overkill, it's unproductive, unmaintainable and ugly as hell. You don't need to use an enum just for the sake of it. You either put an explicit cast or just write a static class with const ints.Marc
Isn't it basically re-implemented Java enum?Rung
One major problem is that you can't use those static readonly constants in switch statements.Bettyannbettye
There are some places where you cannot use static readonly members but only const fields. E.g. Attribute ctor parameter, and also switch statements as Ian said.Presbyterate
@IanGoldby Yes you could use this in a switch statement. Define a static implicit operator that converts to an integer and a class instance can be used in a switch(instance) manner. But then again, you can't use it for labels in a switch statement as those have to be compile time consts.Supersedure
In a project I work on we have enums with metadata. It corresponds to entries in a database that do not change dynamically and thus are static. E.g. a field in the enum has an associated order. We use these in code by using extension methods. E.g. MyEnum.SomeValue.SequenceOrder(). A nedd for enums with variable 'metadata' seems like a design flaw to me.Supersedure
@MikedeKlerk I should have been more explicit - as you mention the labels have to be compile time consts, which is why I said you can't use this in a switch statement. The point is you break encapsulation if you use a label of a literal 2 rather than AccountStatus.Closed. Yes, you could define constants to use in switch labels and use these same constants in your definition of AccountStatus, but I think this compromises the original goal and raison-d'etre of AccountStatus.Bettyannbettye
this also won't allow you to use the values as default values for optional argumentsMountainside
H
39

You can't do implict conversions (except for zero), and you can't write your own instance methods - however, you can probably write your own extension methods:

public enum MyEnum { A, B, C }
public static class MyEnumExt
{
    public static int Value(this MyEnum foo) { return (int)foo; }
    static void Main()
    {
        MyEnum val = MyEnum.A;
        int i = val.Value();
    }
}

This doesn't give you a lot, though (compared to just doing an explicit cast).

One of the main times I've seen people want this is for doing [Flags] manipulation via generics - i.e. a bool IsFlagSet<T>(T value, T flag); method. Unfortunately, C# 3.0 doesn't support operators on generics, but you can get around this using things like this, which make operators fully available with generics.

Heald answered 4/11, 2008 at 12:21 Comment(3)
Yeah, that was one of my most wanted for C#4: #138867 and stackoverflow.com/questions/7244Liu
@Liu - good job it made it, then ;-p The dynamic/operator support didn't make it into the CTP, but I've got a test rig ready-to-roll to compare the two approaches for operators with dynamic (vs generics/Expression) when it gets there.Heald
@Liu - you might want to give the Operator class in MiscUtil a whirl; I'm pretty sure it'll do most of what you want.Heald
C
27
struct PseudoEnum
{
    public const int 
              INPT = 0,
              CTXT = 1,
              OUTP = 2;
};

// ...

var arr = new String[3];

arr[PseudoEnum.CTXT] = "can";
arr[PseudoEnum.INPT] = "use";
arr[PseudoEnum.CTXT] = "as";
arr[PseudoEnum.CTXT] = "array";
arr[PseudoEnum.OUTP] = "index";
Corrigendum answered 15/10, 2012 at 18:27 Comment(2)
but why struct?Breeding
No reason really. You could use static class I suppose. There's no advantage arguing for either case in the final IL code.Corrigendum
O
19

I adapted Mark's excellent RichEnum generic baseclass.

Fixing

  1. a number of compilation problems due to missing bits from his libraries (notably: the resource dependent display names weren't completely removed; they are now)
  2. initialization wasn't perfect: if the first thing you did was access the static .Values property from the base class, you'd get a NPE. Fixed this by forcing the base class to curiously-recursively (CRTP) force the static construction of TDerived just in time during CheckInitialized
  3. finally moved CheckInitialized logic into a static constructor (to avoid the penalty of checking each time, the race condition on multithreaded initialization; perhaps this was an impossibility solved by my bullet 1.?)

Kudos to Mark for the splendid idea + implementation, here's to you all:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Resources;

namespace NMatrix
{

    [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 readonly SortedList<TValue, TDerived> _values = new SortedList<TValue, TDerived>();

        #endregion

        #region Constructors

        protected RichEnum(TValue value)
        {
            this.Value = value;
            _values.Add(value, (TDerived)this);
        }

        #endregion

        #region Properties

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public string Description
        {
            get
            {
                if (_descriptionAttribute != null)
                    return _descriptionAttribute.Description;

                return _name;
            }
        }

        #endregion

        #region Initialization

        static RichEnum()
        {
            var fields = typeof(TDerived)
                .GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public)
                .Where(t => t.FieldType == typeof(TDerived));

            foreach (var field in fields)
            {
                /*var dummy =*/ field.GetValue(null); // forces static initializer to run for TDerived

                TDerived instance = (TDerived)field.GetValue(null);
                instance._name = field.Name;
                                    instance._descriptionAttribute = field.GetCustomAttributes(true).OfType<DescriptionAttribute>().FirstOrDefault();
            }
        }

        #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)
                if (0 == string.Compare(value.Name, name, true))
                    return value;

            return null;
        }
    }
}

A sample of usage that I ran on mono:

using System.ComponentModel;
using System;

namespace NMatrix
{    
    public sealed class MyEnum : RichEnum<int, MyEnum>
    {
        [Description("aap")]  public static readonly MyEnum my_aap   = new MyEnum(63000);
        [Description("noot")] public static readonly MyEnum my_noot  = new MyEnum(63001);
        [Description("mies")] public static readonly MyEnum my_mies  = new MyEnum(63002);

        private MyEnum(int value) : base (value) { } 
        public static implicit operator MyEnum(int value) { return Convert(value); }
    }

    public static class Program
    {
        public static void Main(string[] args)
        {
            foreach (var enumvalue in MyEnum.Values)
                Console.WriteLine("MyEnum {0}: {1} ({2})", (int) enumvalue, enumvalue, enumvalue.Description);
        }
    }
}

Producing the output

[mono] ~/custom/demo @ gmcs test.cs richenum.cs && ./test.exe 
MyEnum 63000: my_aap (aap)
MyEnum 63001: my_noot (noot)
MyEnum 63002: my_mies (mies)

Note: mono 2.6.7 requires an extra explicit cast that is not required when using mono 2.8.2...

Ogham answered 29/3, 2011 at 23:31 Comment(6)
Using .Single() to get the description attribute is not a good idea. If there is no attribute, Single() throws an exception, SingleOrDefault() doesn't.Chloramine
@Chloramine good point, I updated it (using FirstOrDefault, to avoid assuming there is only a single attribute). Whether or not assuming such things is 'a good idea' (or a bad one, for that matter) is of course, dependent on the contextOgham
Love this, but I ran into a problem: on Windows 7/.NET 4.5 this line TDerived instance = (TDerived)field.GetValue(null); results in instance being null. It seems that the Mono runtime must have a different order of type initialization than the .NET one that allows this to work. Puzzling! I had to instead move that code into a static method and call it from the type initializer in the subclass.Deidradeidre
@Deidradeidre i'm experiencing the same issue on .net 4.5.1. It seems to "violate" the C# specification b/c it doesn't initialize the value before first use - at least not when using reflection. I've implemented a workaround which does not require the subclass ('TDerived') to be involved. @ sehe should i edit your answer and add the workaround to your answer or should i post a new answer?Durban
@agentnega, @ sehe in the meantime i've posted the workaround (which should work on mono and "normal" .net) in a separate answer, see: https://mcmap.net/q/156550/-can-we-define-implicit-conversions-of-enums-in-cDurban
@Durban Feel free to edit this answer. I'll try to get back to it laterOgham
O
13

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.

Outdate answered 4/11, 2008 at 12:16 Comment(0)
L
6

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.

Liu answered 4/11, 2008 at 12:18 Comment(0)
D
6

enums are largely useless for me because of this, OP.

I end up doing pic-related all the time:

the simple solution

classic example problem is the VirtualKey set for detecting keypresses.

enum VKeys : ushort
{
a = 1,
b = 2,
c = 3
}
// the goal is to index the array using predefined constants
int[] array = new int[500];
var x = array[VKeys.VK_LSHIFT]; 

problem here is you can't index the array with the enum because it can't implicitly convert enum to ushort (even though we even based the enum on ushort)

in this specific context, enums are obsoleted by the following datastructure . . . .

public static class VKeys
{
public const ushort
a = 1,
b = 2, 
c = 3;
}
Diseased answered 23/8, 2018 at 4:27 Comment(0)
G
4

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);
    }
}
Grenoble answered 25/8, 2016 at 9:13 Comment(0)
M
3

I created this utility to help me convert an Enum to PrimitiveEnum and PrimitiveEnum to byte, sbyte, short, ushort, int, uint, long, or ulong.

So, this technically converts any enum to any its primitive value.

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

PrimitiveEnum number = MyEnum.one;
long i = number;

See commit at https://github.com/McKabue/McKabue.Extentions.Utility/blob/master/src/McKabue.Extentions.Utility/Enums/PrimitiveEnum.cs

using System;

namespace McKabue.Extentions.Utility.Enums
{
    /// <summary>
    /// <see href="https://mcmap.net/q/156550/-can-we-define-implicit-conversions-of-enums-in-c/3563013">
    /// Can we define implicit conversions of enums in c#?
    /// </see>
    /// </summary>
    public struct PrimitiveEnum
    {
        private Enum _enum;

        public PrimitiveEnum(Enum _enum)
        {
            this._enum = _enum;
        }

        public Enum Enum => _enum;


        public static implicit operator PrimitiveEnum(Enum _enum)
        {
            return new PrimitiveEnum(_enum);
        }

        public static implicit operator Enum(PrimitiveEnum primitiveEnum)
        {
            return primitiveEnum.Enum;
        }

        public static implicit operator byte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToByte(primitiveEnum.Enum);
        }

        public static implicit operator sbyte(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToSByte(primitiveEnum.Enum);
        }

        public static implicit operator short(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt16(primitiveEnum.Enum);
        }

        public static implicit operator ushort(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt16(primitiveEnum.Enum);
        }

        public static implicit operator int(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt32(primitiveEnum.Enum);
        }

        public static implicit operator uint(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt32(primitiveEnum.Enum);
        }

        public static implicit operator long(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToInt64(primitiveEnum.Enum);
        }

        public static implicit operator ulong(PrimitiveEnum primitiveEnum)
        {
            return Convert.ToUInt64(primitiveEnum.Enum);
        }
    }
}
Middendorf answered 29/1, 2020 at 14:9 Comment(1)
+1 I have a game framework with many things identified by uints that the game itself usually makes enums for, but the framework knows nothing about. Having to (uint) when calling the framework was a pain. Your idea backwards works perfectly. Instead of the struct storing the Enum, I have a struct IdNumber which stores the uint but implicitly converts from the Enums the game uses. Instead of typing the framework's params as uint, I can type them IdNumber, and the framework can internally pass them around efficiently, even doing integral ops on them.Stancil
P
1

If you define the base of the enum as a long then you can perform explicit conversion. I don't know if you can use implicit conversions as enums cannot have methods defined on them.

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

MyEnum number = MyEnum.one;
long i = (long)number;

Also, be aware with this that an uninitalised enumeration will default to the 0 value, or the first item - so in the situation above it would probably be best to define zero = 0 as well.

Prestige answered 4/11, 2008 at 12:11 Comment(7)
You don't need the : long here; the explicit conversion would work fine without it. The only legal implicit conversion is zero.Heald
I thought all enums where longs by default? Therefore explicit conversions to longs already exists?Roane
No; the default enum is Int32Heald
ints by default, you can make then any whole-numeric type (byte, short, int, long)Liu
See: enum Foo { A, B, C} Console.WriteLine(Enum.GetUnderlyingType(typeof(Foo)));Heald
WHy is this marked as answer and has so much points ? This is NOT relevant to the OP question !!! He is talking about IMPLICIT Conversion... The added value is nil.Haywoodhayyim
The question already implies that explicit casts are understood, the question is equivalent to asking "How do I avoid explicitly casting?", to which THIS post does not apply.Hoagy
D
1

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!

Durban answered 22/5, 2015 at 11:47 Comment(0)
S
1

Here's a different flavour based on adminSoftDK's answer.

/// <summary>
/// Based on https://datatracker.ietf.org/doc/html/rfc4346#appendix-A.1
/// </summary>
[DebuggerDisplay("{_value}")]
public struct HandshakeContentType
{
    #region Types
    public const byte ChangeCipher = 0x14;
    public const byte Alert = 0x15;
    public const byte Handshake = 0x16;
    public const byte ApplicationData = 0x17;
    #endregion

    byte _value;
    private HandshakeContentType(byte value)
    {
        _value = value;

        switch (_value)
        {
            case ChangeCipher:
            case Alert:
            case Handshake:
            case ApplicationData:
                break;

            default:
                throw new InvalidOperationException($"An invalid handshake content type (${value}) was provided.");
        }
    }

    #region Methods
    public static implicit operator byte(HandshakeContentType type) => type._value;
    public static implicit operator HandshakeContentType(byte b) => new HandshakeContentType(b);
    #endregion
}

This allows you to use this struct with switch statements which I think is pretty awesome.

Smilacaceous answered 13/9, 2021 at 19:39 Comment(0)
A
0

@BatteryBackupUnit Hey this sounds like a cool solution but could you explain this part here?

Since im getting with .NET 4.7.2 an "InvalidCastException" out of this sadly :/

 _name = new Lazy<string>(
                () => EnumInstanceToNameMapping
                     .First(x => ReferenceEquals(this, x.Instance))
                     .Name);

I dont know why, i have created a derived type of the RichEnum and initialized as everything u did in the example but i getthis annyoingg exception..

Would be glad of some help to this since i like this approach alot tbh.

Ashcan answered 6/11, 2020 at 8:49 Comment(0)
D
0

I don't have enough rep to add a comment, but I was inspired by the 'struct' comment here: https://mcmap.net/q/156550/-can-we-define-implicit-conversions-of-enums-in-c

Here is how I did it:

public enum DaysOfWeek
{
   Sunday = 0,
   Monday = 1,
   Tuesday = 2,
   Wednesday = 3,
   Thursday = 4,
   Friday = 5,
   Saturday = 7,
}

public struct Weekends
{
   private Weekends(DaysOfWeek day){ Day = day; }
   public readonly DaysOfWeek Day;
   public static Weekends Sunday = new(DaysOfWeek.Sunday);
   public static Weekends Saturday = new(DaysOfWeek.Saturday);
   
   public static implicit operator DaysOfWeek(Weekends value) => value.Mode;

}

I feel this gets the best of both worlds here, since you get your super enum, and easily accessible structs that are statically accessibly acting as subsets of the superenum.

Dachy answered 25/4, 2022 at 20:51 Comment(0)
P
-2

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.

Peahen answered 4/11, 2008 at 12:19 Comment(1)
I have a use for implicit conversion of enums. Using SPMetal to generate LINQ to SharePoint classes in multiple sites of the same site collection. Some of my lists are in one subsite, others in a different subsite. Due to how SPMetal generates the code, site columns used in multiple lists of the collection may be defined in multiple namespaces. However, I need to convert between the choice field enum in one namespace to the same enum in another namespace. Implicit conversion would be very helpful.Litt

© 2022 - 2024 — McMap. All rights reserved.