Possiamo definire conversioni implicite di enumerazione in c #?

È ansible definire una conversione implicita di enumerazioni in c #?

qualcosa che potrebbe raggiungere questo?

public enum MyEnum { one = 1, two = 2 } MyEnum number = MyEnum.one; long i = number; 

Se no, perché no?

Per ulteriori discussioni e idee su questo, ho seguito come attualmente gestisco questo: Migliorare l’enum di C #

C’è una soluzione. Considera quanto segue:

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

Le offerte sopra implicite di conversione:

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

Questo è un bel po ‘più di lavoro che dichiarare un normale enum (anche se puoi rifattorizzare alcuni di questi in una comune class base generica). Puoi andare ancora oltre avendo la class base implementare IComparable & IEquatable, così come aggiungere metodi per restituire il valore di DescriptionAttributes, nomi dichiarati, ecc, ecc.

Ho scritto una class base (RichEnum <>) per gestire la maggior parte del lavoro grunt, il che facilita la suddetta dichiarazione di enumerazione a:

 public sealed class AccountStatus : RichEnum { 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); } } 

La class base (RichEnum) è elencata di seguito.

 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 : IEquatable, IComparable, IComparable, IComparer where TValue : struct , IComparable, IEquatable where TDerived : RichEnum { #region Backing Fields ///  /// The value of the enum item ///  public readonly TValue Value; ///  /// The public field name, determined from reflection ///  private string _name; ///  /// The DescriptionAttribute, if any, linked to the declaring field ///  private DescriptionAttribute _descriptionAttribute; ///  /// Reverse lookup to convert values back to local instances ///  private static SortedList _values; private static bool _isInitialized; #endregion #region Constructors protected RichEnum(TValue value) { if (_values == null) _values = new SortedList(); 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(); 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 value) { return value.Value; } public static implicit operator RichEnum(TValue value) { return _values[value]; } public static implicit operator TDerived(RichEnum value) { return value; } public override string ToString() { return _name; } #endregion #region IEquatable 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.Equals(TDerived other) { return Value.Equals(other.Value); } public override int GetHashCode() { return Value.GetHashCode(); } #endregion #region IComparable Members int IComparable.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.Compare(TDerived x, TDerived y) { return (x == null) ? -1 : (y == null) ? 1 : x.Value.CompareTo(y.Value); } #endregion public static IEnumerable 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; } } } 

Non è ansible eseguire conversioni implicite (tranne zero) e non è ansible scrivere i propri metodi di istanza, tuttavia è ansible scrivere i propri metodi di estensione:

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

Questo non ti dà molto, però (rispetto al solo fare un cast esplicito).

Una delle principali occasioni in cui ho visto le persone lo desidera è per la manipolazione di [Flags] tramite generici, ad esempio un bool IsFlagSet(T value, T flag); metodo. Sfortunatamente, C # 3.0 non supporta gli operatori sui generici, ma puoi aggirare questo usando cose come questa , che rendono gli operatori completamente disponibili con i generici.

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

Ho adattato l’eccellente base generica RichEnum di Mark.

Fissaggio

  1. un numero di problemi di compilazione dovuti alla mancanza di bit dalle sue librerie (in particolare: i nomi di visualizzazione dipendenti dalla risorsa non sono stati completamente rimossi; ora sono)
  2. l’inizializzazione non era perfetta: se la prima cosa che hai fatto era accedere alla proprietà statica. Valori dalla class base, avresti ottenuto un NPE. Risolto questo problema costringendo la class base a curiosamente-ricorsivamente ( CRTP ) a forzare la costruzione statica di TDerived just in time durante CheckInitialized
  3. alla fine ho spostato la logica CheckInitialized in un costruttore statico (per evitare la penalità di controllare ogni volta, la condizione di competizione sull’inizializzazione multithread, forse questa era un’impossibilità risolta dal mio proiettile 1.?)

Complimenti a Mark per la splendida idea + implementazione, ecco a voi tutti:

 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 : IEquatable, IComparable, IComparable, IComparer where TValue : struct, IComparable, IEquatable where TDerived : RichEnum { #region Backing Fields ///  /// The value of the enum item ///  public readonly TValue Value; ///  /// The public field name, determined from reflection ///  private string _name; ///  /// The DescriptionAttribute, if any, linked to the declaring field ///  private DescriptionAttribute _descriptionAttribute; ///  /// Reverse lookup to convert values back to local instances ///  private static readonly SortedList _values = new SortedList(); #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().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 value) { return value.Value; } public static implicit operator RichEnum(TValue value) { return _values[value]; } public static implicit operator TDerived(RichEnum value) { return value; } public override string ToString() { return _name; } #endregion #region IEquatable 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.Equals(TDerived other) { return Value.Equals(other.Value); } public override int GetHashCode() { return Value.GetHashCode(); } #endregion #region IComparable Members int IComparable.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.Compare(TDerived x, TDerived y) { return (x == null) ? -1 : (y == null) ? 1 : x.Value.CompareTo(y.Value); } #endregion public static IEnumerable 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; } } } 

Un esempio di utilizzo che ho eseguito su mono:

 using System.ComponentModel; using System; namespace NMatrix { public sealed class MyEnum : RichEnum { [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); } } } 

Produrre l’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) 

Nota: il mono 2.6.7 richiede un cast esplicito extra che non è richiesto quando si usa il mono 2.8.2 …

Se si definisce la base dell’enumerazione come lunga, è ansible eseguire una conversione esplicita. Non so se è ansible utilizzare le conversioni implicite poiché le enumerazioni non possono avere metodi definiti su di esse.

 public enum MyEnum : long { one = 1, two = 2, } MyEnum number = MyEnum.one; long i = (long)number; 

Inoltre, tieni presente che un’enumerazione non finalizzata imposterà automaticamente il valore 0 o il primo elemento, quindi nella situazione precedente sarebbe probabilmente meglio definire anche zero = 0 .

Probabilmente potresti, ma non per l’enum (non puoi aggiungere un metodo ad esso). È ansible aggiungere una conversione implicita alla propria class per consentire la conversione di un enum in esso,

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

La domanda sarebbe perché?

In generale .Net evita (e dovresti) anche qualsiasi conversione implicita in cui i dati possano essere persi.

Non è ansible dichiarare conversioni implicite su tipi enum, perché non possono definire metodi. La parola chiave implicita C # viene compilata in un metodo che inizia con “op_” e in questo caso non funzionerebbe.

Ho risolto un problema con la risposta di sehe durante l’esecuzione del codice su MS .net (non Mono). Per me, in particolare, il problema si è verificato in. 4.5.1 ma anche altre versioni sembrano interessate.

Il problema

l’accesso a un public static TDervied MyEnumValue per riflessione (tramite FieldInfo.GetValue(null) non inizializza il campo specificato.

La soluzione

Invece di assegnare nomi alle istanze di TDerived statico di RichEnum questo viene fatto pigramente al primo accesso di TDerived.Name . Il codice:

 public abstract class RichEnum : EquatableBase where TValue : struct, IComparable, IEquatable where TDerived : RichEnum { // 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´ 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 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 Values = new SortedList(); public readonly TValue Value; private readonly Lazy _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( () => EnumInstanceToNameMapping .First(x => ReferenceEquals(this, x.Instance)) .Name); } public string Name { get { return _name.Value; } } public static implicit operator TValue(RichEnum 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 _instance; public EnumInstanceReflectionInfo(FieldInfo field) { _field = field; _instance = new Lazy(() => (TDerived)field.GetValue(null)); } public TDerived Instance { get { return _instance.Value; } } public string Name { get { return _field.Name; } } } } 

che – nel mio caso – si basa su EquatableBase :

 public abstract class EquatableBase 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(); } 

Nota

Il codice sopra non incorpora tutte le caratteristiche della risposta originale di Mark !

Grazie

Grazie a Mark per aver fornito la sua implementazione RichEnum e grazie a sehe per aver apportato alcuni miglioramenti!

Ho trovato la soluzione ancora più semplice presa da qui https://codereview.stackexchange.com/questions/7566/enum-vs-int-wrapper-struct Ho incollato il codice qui sotto da quel link nel caso in cui non funzioni in futuro.

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

le enumerazioni sono in gran parte inutili per me per questo, OP.

Finisco sempre con le foto correlate:

la soluzione semplice

classico problema di esempio è il set VirtualKey per il rilevamento di pressioni di tasti.

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

il problema qui è che non puoi indicizzare l’array con l’enumerazione perché non può convertire implicitamente enum in ushort (anche se abbiamo anche basato l’enum su ushort)

in questo specifico contesto, le enumerazioni sono state rese obsolete dalla seguente infrastruttura. . . .

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

L’introduzione di conversioni implicite per i tipi di enumerazione comprometterebbe la sicurezza del tipo, quindi non lo consiglierei. Perché vorresti farlo? L’unico caso d’uso per questo ho visto è quando si desidera mettere i valori enum in una struttura con un layout predefinito. Ma anche in questo caso, puoi usare il tipo di enum nella struttura e dire al Marshaller cosa dovrebbe fare con questo.