How to Define Implicit Conversions of Enums in C#

Can we define implicit conversions of enums in c#?

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

Create implicit cast for enum value

You can create a converter method that converts a MessageBoxButtons to a MyMessageBoxButton, create an overload of your Show method that takes a MessageBoxButtons, and then in that method you would do the conversion and call your existing Show method.

Since you don't have matching enum values for all items (like AbortRetryIgnore), you will have to decide which button combination to show if someone selects one of those:

public static MyMessageBoxButton ConvertToMyMessageBoxButton(MessageBoxButtons input)
{
MyMessageBoxButton result;

switch (input)
{
case MessageBoxButtons.OK:
result = MyMessageBoxButton.OK;
break;
case MessageBoxButtons.OKCancel:
result = MyMessageBoxButton.OKCancel;
break;
case MessageBoxButtons.RetryCancel:
result = MyMessageBoxButton.NowLaterCancel;
break;
case MessageBoxButtons.YesNo:
result = MyMessageBoxButton.YesNo;
break;
case MessageBoxButtons.YesNoCancel:
result = MyMessageBoxButton.YesNoCancel;
break;
default:
// AbortRetryIgnore will fall through to this
result = MyMessageBoxButton.OKCancel;
break;
}

return result;
}

Then you would use the Convert method in an overload of your Show method, to do the conversion and call your original Show:

public static MessageBoxResult Show(String caption, String message, 
MessageBoxButtons button, MessageBoxImage icon)
{
return Show(caption, message, ConvertToMyMessageBoxButton(button), icon);
}

Implicit conversion of 0 to enums

As you've noted, 0 | 0 | E.X gets bound as (0 | 0) | E.X.

Eric noted that the compiler doesn't follow the spec for 0 | 0 | E.X:

After we've got a complete parse tree we walk through the parse tree making sure that all the types work out. Unfortunately the initial type binding pass bizarrely enough does arithmetic optimizations. It detects the 0|something and aggressively replaces it with just something , so as far as the compiler is concerned, the sixth case is the same as the second, which is legal. Argh!

Eric notes in the comments:

but (7-7)|E.X does correctly produce an error

It seems that Roslyn is a bit smarter about folding constants than the native compiler was. It's likely that they were aiming for efficiency here, without being concerned about preserving bug-for-bug behaviour in an edge-case.

Exactly the same problem now seems to apply to 7 - 7, or any other expression which the compiler can evaluate to 0 during that initial type binding pass, for the same reason.

I think the constant folding is happening here:

newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight);
if (newValue != null)
{
return ConstantValue.Create(newValue, resultType);
}

As you can see, this creates a new ConstantValue. So (0 | 0) | E.X gets folded to 0 | E.X, where that first 0 is a constant. When the compiler comes to fold 0 | E.X, it isn't aware that the 0 is not a literal 0 in the original source, but is instead a compiler-generated constant, and so folds it as if you had written 0 | E.X originally.

Exactly the same is happening with your other examples, and I think it's done by the same bit of code. 1 - 1 gets folded into a constant 0, as do the others. This will happen with any expression that the compiler can evaluate to 0 at compile-time.

C# enum - why does *implicit* casting from 0 work?

It's this way because that's what the spec says...

This is another reason why it's always a good idea to give all your enums an item with value 0, 'cos you're likely to get them with that value sometimes.


The appropriate section in the C# language spec 6.1.3:

6.1.3 Implicit enumeration conversions

An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type and to any nullable-type whose underlying type is an enum-type. In the latter case the conversion is evaluated by converting to the underlying enum-type and wrapping the result (§4.1.10).

As to why it's that way - well, I guess only someone on the language committee that decides these things would know.

In fact, we do have something like that if you look at rawling's comment to the original question.

Why switch for enum accepts implicit conversion to 0 but no for any other integer?

From ECMA-334 (C# Language Specification)

13.1.3 Implicit enumeration conversions

An implicit enumeration conversion permits the decimal-integer-literal
0 to be converted to any enum-type.

enum's default value is 0 and at compile time it is known that is why it is allowed in the switch statement. For value other than 0, it can't be determine at compile time whether this value will exist in the enum or not.

enum (C# Reference)

Assigning additional values new versions of enums, or changing the
values of the enum members in a new version, can cause problems for
dependant source code. It is often the case that enum values are
used in switch statements, and if additional elements have been added
to the enum type, the test for default values can return true
unexpectedly.

Cannot implicitly cast enum to integer in C#

If you could do that, you'd lose half the point of using enums at all - the point is to keep them as separate types to avoid you making mistakes involving using types incorrectly.

It seems to me like your dictionary should actually have FieldType as the key - at which point you'd have a safer approach. With your current expectation, you're expecting to be able to use any (int-based) enum type as the key, including things like FileShare which are clearly inappropriate.

So basically, you should ask yourself whether fundamentally you've got a map from field type to type, or whether you've really got a numeric map. To put it another way, without looking at FieldType at all, does an entry in the map from the number 1 to some Type mean anything? What does the 1 value represent there?

Why does C# 3 allow the implicit conversion of literal zero (0) to any Enum?

C# has always allowed the implicit conversion of the literal 0 to any Enum value. What has changed is how the rule is applied to other constant expressions. It was made to be more consistent and allow any constant expressions which evaluates to 0 to be implicitly convertible to an enum.

The example you gave produces the same behavior in all versions of C#.

Here is an example of changed behavior (directly from the linked documentation)

public enum E
{
Zero = 0,
One = 1,
}

class A
{
public static A(string s, object o)
{ System.Console.WriteLine("{0} => A(object)", s); }

public static A(string s, E e)
{ System.Console.WriteLine("{0} => A(Enum E)", s); }

static void Main()
{
A a1 = new A("0", 0);
A a3 = new A("(int) E.Zero", (int) E.Zero);
}
}

Visual C# 2005 output:


0 => A(Enum E)
(int) E.Zero => A(object)

Visual C# 2008 output:


0 => A(Enum E)
(int) E.Zero => A(Enum E)

Can I avoid casting an enum value when I try to use or return it?

enums are supposed to be type safe. I think they didn't make them implicitly castable to discourage other uses. Although the framework allows you to assign a constant value to them, you should reconsider your intent. If you primarily use the enum for storing constant values, consider using a static class:

public static class ReturnValue
{
public const int Success = 0;
public const int FailReason1 = 1;
public const int FailReason2 = 2;
//Etc...
}

That lets you do this.

public static int main(string[] args){
return ReturnValue.Success;
}

EDIT

When you do want to provide values to an enum is when you want to combine them. See the below example:

[Flags] // indicates bitwise operations occur on this enum
public enum DaysOfWeek : byte // byte type to limit size
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64,
Weekend = Sunday | Saturday,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday
}

This enum can then be consumed by using bitwise math. See the below example for some applications.

public static class DaysOfWeekEvaluator
{
public static bool IsWeekends(DaysOfWeek days)
{
return (days & DaysOfWeek.Weekend) == DaysOfWeek.Weekend;
}

public static bool IsAllWeekdays(DaysOfWeek days)
{
return (days & DaysOfWeek.Weekdays) == DaysOfWeek.Weekdays;
}

public static bool HasWeekdays(DaysOfWeek days)
{
return ((int) (days & DaysOfWeek.Weekdays)) > 0;
}

public static bool HasWeekendDays(DaysOfWeek days)
{
return ((int) (days & DaysOfWeek.Weekend)) > 0;
}
}

implicit type conversion for enum type in C

What you have is an enum, not a union. And what it seems like you're asking is if you can assign specific values to enum constants. You can do so like this:

enum COMP { 
LessThan=-1,
Equal=0,
GreaterThan=1
};

Also, enums are considered integer types, so you can safely convert to or from int.

C# enum to string auto-conversion?

No. An enum is its own type, so if you want to convert it to something else, you have to do some work.

However, depending on what you're doing with it, some methods will call ToString() on it automatically for you. For example, you can do:

Console.Writeline(Rank.A);


Related Topics



Leave a reply



Submit