我们可以在 c# 中定义枚举的隐式转换吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/261663/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can we define implicit conversions of enums in c#?
提问by Adam Naylor
Is it possible to define an implicit conversion of enums in c#?
是否可以在 c# 中定义枚举的隐式转换?
something that could achieve this?
可以实现这一目标的东西?
public enum MyEnum
{
one = 1, two = 2
}
MyEnum number = MyEnum.one;
long i = number;
If not, why not?
如果没有,为什么不呢?
采纳答案by Mark
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.
这比声明一个普通的枚举要多得多(尽管你可以将上面的一些重构为一个通用的通用基类)。通过让基类实现 IComparable 和 IEquatable,以及添加方法来返回 DescriptionAttributes 的值、声明的名称等,您可以更进一步。
I wrote a base class (RichEnum<>) to handle most fo the grunt work, which eases the above declaration of enums down to:
我编写了一个基类 (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);
}
}
The base class (RichEnum) is listed below.
下面列出了基类 (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;
}
}
}
回答by Ash
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.
如果将枚举的基数定义为 long,则可以执行显式转换。我不知道您是否可以使用隐式转换,因为枚举不能在其上定义方法。
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.
另外,请注意,未初始化的枚举将默认为 0 值或第一项 - 因此在上述情况下,最好也进行定义zero = 0
。
回答by Igal Tabachnik
回答by Keith
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.
一般来说,.Net 避免(你也应该)避免任何可能丢失数据的隐式转换。
回答by OregonGhost
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.
为枚举类型引入隐式转换会破坏类型安全,所以我不建议这样做。你为什么想这么做?我见过的唯一用例是当您想将枚举值放入具有预定义布局的结构中时。但即便如此,您也可以在结构中使用枚举类型,并告诉 Marshaller 他应该如何处理它。
回答by Marc Gravell
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.
我见过人们希望这样做的主要时间之一是[Flags]
通过泛型进行操作 - 即一种bool IsFlagSet<T>(T value, T flag);
方法。不幸的是,C# 3.0 不支持泛型上的运算符,但您可以使用类似这样的东西来解决这个问题,这使得运算符完全适用于泛型。
回答by sehe
I adapted Mark's excellent RichEnum generic baseclass.
我改编了 Mark 优秀的 RichEnum 通用基类。
Fixing
定影
- 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)
- 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
- 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.?)
- 由于他的库中缺少位而导致的一些编译问题(特别是:依赖于资源的显示名称并未完全删除;现在已删除)
- 初始化并不完美:如果你做的第一件事是从基类访问静态 .Values 属性,你会得到一个 NPE。通过强制基类好奇地递归( CRTP) 在 CheckInitialized 期间及时强制静态构造 TDerived 解决了这个问题
- 最后将 CheckInitialized 逻辑移动到静态构造函数中(为了避免每次检查的惩罚,多线程初始化时的竞争条件;也许这是我的第 1 项解决的不可能的事情。?)
Kudos to Mark for the splendid idea + implementation, here's to you all:
感谢 Mark 出色的想法 + 实施,这里是给你们的:
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...
注意:mono 2.6.7 需要额外的显式转换,而在使用 mono 2.8.2 时则不需要。
回答by Glenn Slayden
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";
回答by BatteryBackupUnit
I've worked around an issue with sehe's answerwhen 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.
在 MS .net(非 Mono)上运行代码时,我解决了sehe 的答案问题。对我来说,问题发生在 .net 4.5.1 上,但其他版本似乎也受到了影响。
The issue
问题
accessing a public static TDervied MyEnumValue
by reflection (via FieldInfo.GetValue(null)
does notinitialize said field.
访问public static TDervied MyEnumValue
由反射(通过FieldInfo.GetValue(null)
不未初始化所述场。
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:
而不是指定名字的TDerived
时的静态初始化实例RichEnum<TValue, 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; } }
}
}
which - in my case - is based upon EquatableBase<T>
:
这 - 就我而言 - 基于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!
上面的代码没有包含Mark原始答案的所有功能!
Thanks
谢谢
Thanks to Markfor providing his RichEnum
implementation and thanks to sehefor providing some improvements!
回答by adminSoftDK
I found even easier solution taken from here https://codereview.stackexchange.com/questions/7566/enum-vs-int-wrapper-structI pasted the code below from that link just in case it does not work in the future.
我从这里找到了更简单的解决方案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);
}
}