我们可以在 c # 中定义枚举的隐式转换吗?

是否可以在 c # 中定义枚举的隐式转换?

能做到这一点的东西吗?

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


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

如果不是,为什么不呢?

79060 次浏览

如果您将枚举的基底定义为 long,则可以执行显式转换。我不知道是否可以使用隐式转换,因为枚举不能在它们上面定义方法。

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


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

另外,请注意,一个未初始化的枚举将默认为0值,或第一个项目-因此在上面的情况下,最好也定义 zero = 0

不能在枚举类型上声明隐式转换,因为它们不能定义方法。C # 含蓄关键字编译成以‘ op _’开头的方法,在这种情况下不起作用。

您可能可以,但是不能为枚举添加方法(您不能向它添加方法)。您可以向自己的类添加隐式转换,以允许将枚举转换为它,

public class MyClass {


public static implicit operator MyClass ( MyEnum input ) {
//...
}
}


MyClass m = MyEnum.One;

问题是为什么?

通常.Net 避免(您也应该避免)任何可能丢失数据的隐式转换。

为枚举类型引入隐式转换会破坏类型安全,因此我不建议这样做。你为什么要这么做?我看到的唯一用例是当您想要将枚举值放入具有预定义布局的结构中时。但是即使这样,您也可以在结构中使用枚举类型,并告诉 Marshall 应该如何处理它。

你不能进行隐式转换(除了零) ,也不能编写自己的实例方法——不过,你可以编写自己的扩展方法:

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

不过,这并没有给您带来很多好处(与只进行显式强制转换相比)。

我见过人们想要这样做的主要时间之一是通过泛型来操作 [Flags]-也就是 bool IsFlagSet<T>(T value, T flag);方法。遗憾的是,C # 3.0不支持泛型上的运算符,但是您可以使用 像这样的事情来解决这个问题,它使得泛型上的运算符完全可用。

有一个解决办法,考虑以下几点:

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

以上提供了隐含的转换:

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

这比声明一个普通的枚举要多一些工作(尽管您可以将上面的一些内容重构为一个通用的泛型基类)。您可以更进一步,让基类实现 ICompable & IEqutable,以及添加方法来返回 DescriptionAttritribute 的值、声明的名称等等。

我编写了一个基类(RichEnum < >)来处理大部分繁琐的工作,它将上面的枚举声明简化为:

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

下面列出了基类(RichEnum)。

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

我改编了 Mark 优秀的 RichEnum 通用基类。

修理

  1. 许多编译问题都是由于库中丢失的位造成的(值得注意的是: 依赖于资源的显示名称没有完全删除; 现在已经删除了)
  2. 初始化并不完美: 如果您做的第一件事是访问静态。属性,则会得到一个 NPE。通过强制基类到 好奇地,递归地(CRTP)来修正这个问题,在 CheckInitialization 期间及时强制 TDerived 的静态构造
  3. 最后,将 CheckInitialization 逻辑移动到静态构造函数中(为了避免每次检查的代价,多线程初始化的竞态条件; 也许这是不可能的,我的项目符号1解决了这个问题。?)

马克的绝妙想法 + 实现值得称赞,这是给你们所有人的:

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

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

生产产品

[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)

注意: mono 2.6.7需要额外的显式强制转换,这在使用 mono 2.8.2时是不需要的..。

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

在 MS 上运行代码时,我已经解决了 这就是答案的一个问题。净(非单核细胞增多症)。对我来说,这个问题发生在。Net 4.5.1,但其他版本似乎也受到了影响。

问题

通过反射访问 public static TDervied MyEnumValue(通过 FieldInfo.GetValue(null) 没有初始化所述字段)。

变通方案

与在 RichEnum<TValue, TDerived>的静态初始值设定项上为 TDerived实例分配名称不同,这是在 TDerived.Name的第一次访问时延迟完成的。密码:

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

在我看来,这是基于 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();
}

注意

上面的代码没有包含 马克原始答案的所有特性!

谢谢

感谢 马克提供了他的 RichEnum实现,感谢 再见提供了一些改进!

我发现更容易的解决方案从这里采取的 https://codereview.stackexchange.com/questions/7566/enum-vs-int-wrapper-struct我粘贴的代码下面的链接,只是以防万一它不工作的未来。

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

枚举很大程度上是没有用的,因为这一点,OP。

最后我一直在拍照:

简单的解决办法

典型的示例问题是用于检测按键的 VirtualKey 集。

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

这里的问题是不能使用枚举对数组进行索引,因为它不能隐式地将枚举转换为 usshort (即使我们甚至将枚举基于 usshort)

在此特定上下文中,枚举被以下数据结构所取代 . . . .

public static class VKeys
{
public const ushort
a = 1,
b = 2,
c = 3;
}

我创建这个实用程序是为了帮助我将 Enum转换为 原始 Enum,将 原始 Enum转换为 byte, sbyte, short, ushort, int, uint, long, or ulong

因此,这在技术上将任何枚举转换为任何其基本值。

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


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

参见 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://stackoverflow.com/q/261663/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);
}
}
}

@ BatteryBackupUnit 嘿,这听起来像一个很酷的解决方案,但你能解释这一部分吗?

自从我用.NET 4.7.2得到一个“ InvalidCastException”之后,这个例外令人伤心:/

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

我不知道为什么,我已经创建了一个导出类型的 RichEnum 和初始化的一切,你做的例子,但我得到这个任何异常。.

我会很高兴对此有所帮助,因为我非常喜欢这种方法。

这是基于 AdminSoftDK 的回答的不同风味。

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

这允许您使用这个 structswitch语句,我认为这非常棒。

我没有足够的名气来添加评论,但是我受到了这里“ struct”评论的启发: Https://stackoverflow.com/a/39141171/12135042

我是这样做的:

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;


}

我觉得这样做是两全其美的,因为您得到了超枚举,并且容易访问的结构静态地可访问地充当超枚举的子集。