134 votes

Peut-on définir des conversions implicites d'enums en c# ?

Est-il possible de définir une conversion implicite des enums en c# ?

quelque chose qui pourrait y parvenir ?

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

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

Si non, pourquoi ?

2 votes

J'aimerais aussi le faire. Nous avons un enum enum YesNo {Yes, No} qui pourrait implicitement se convertir en bool.

0 votes

Noter que ce concept désactive la vérification de la sécurité des types du compilateur. À plus long terme, un raccourci de conversion explicite, comme un '~' de fin de ligne, pourrait être préférable.

0 votes

Le lien n'est plus valide. Pouvons-nous supprimer le lien ou rediffuser le site Web quelque part ?

133voto

Mark Points 5924

Il existe une solution. Considérez ce qui suit :

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

Ce qui précède offre une conversion implicite :

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

C'est un peu plus de travail que de déclarer un enum normal (bien que vous puissiez remanier certains des éléments ci-dessus dans une classe de base générique commune). Vous pouvez aller encore plus loin en faisant en sorte que la classe de base implémente IComparable & IEquatable, ainsi qu'en ajoutant des méthodes pour retourner la valeur de DescriptionAttributes, les noms déclarés, etc, etc.

J'ai écrit une classe de base (RichEnum<>) pour gérer la plupart du travail de grunt, ce qui facilite la déclaration des enums ci-dessus :

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

La classe de base (RichEnum) est indiquée ci-dessous.

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

0 votes

Correction d'une petite erreur involontaire dans le post :-) C'est public static implicit operator AccountStatus(byte value) { return Convert(value) ; } PAS retourner Convert(byte) ;

0 votes

J'ai fait compiler cette classe de base. Cela vous dérange si j'édite les changements ?

0 votes

Dans votre premier exemple (approche sans classe de base), l'option SortedList doit être défini avant (au-dessus dans le code) l'élément AccountStatus sinon le constructeur se plaindra pendant l'exécution que la SortedList est nulle.

36voto

Marc Gravell Points 482669

Vous ne pouvez pas effectuer de conversions implicites (sauf pour le zéro), et vous ne pouvez pas écrire vos propres méthodes d'instance - cependant, vous pouvez probablement écrire vos propres méthodes d'extension :

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

Cela ne vous donne pas grand-chose, cependant (par rapport à un casting explicite).

L'une des principales fois où j'ai vu des gens vouloir ça, c'est pour faire [Flags] manipulation via des génériques - c'est-à-dire une bool IsFlagSet<T>(T value, T flag); méthode. Malheureusement, C# 3.0 ne prend pas en charge les opérateurs sur les génériques, mais vous pouvez contourner ce problème en utilisant les méthodes suivantes des choses comme ça qui rendent les opérateurs entièrement disponibles avec les génériques.

0 votes

Oui, c'était l'une des choses que je voulais le plus pour le C#4 : stackoverflow.com/questions/138367/ y stackoverflow.com/questions/7244

0 votes

@Keith - c'est une bonne chose qu'il soit arrivé, alors ;-p Le support dynamique/opérateur n'est pas arrivé dans la CTP, mais j'ai un banc d'essai prêt à rouler pour comparer les deux approches pour les opérateurs avec dynamique (vs génériques/expression) quand il y arrivera.

0 votes

@Keith - vous pouvez essayer la classe Operator de MiscUtil ; je suis sûr qu'elle fera la plupart des choses que vous voulez.

18voto

sehe Points 123151

J'ai adapté l'excellente classe de base générique RichEnum de Mark.

Correction de

  1. un certain nombre de problèmes de compilation dus à des éléments manquants dans ses bibliothèques (notamment : les noms d'affichage dépendant des ressources n'étaient pas complètement supprimés ; ils le sont maintenant)
  2. L'initialisation n'était pas parfaite : si la première chose que vous faisiez était d'accéder à la propriété statique .Values de la classe de base, vous obteniez un NPE. Nous avons corrigé ce problème en forçant la classe de base à curieusement-récursivement ( CRTP ) force la construction statique de TDerived juste à temps pendant CheckInitialized
  3. enfin, déplacer la logique CheckInitialized dans un constructeur statique (pour éviter la pénalité de vérifier à chaque fois, la condition de course sur l'initialisation multithread ; peut-être que c'était une impossibilité résolue par mon point 1. ?)

Bravo à Mark pour cette splendide idée et sa mise en œuvre, à vous tous :

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

Un échantillon de l'utilisation que j'ai faite de 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);
        }
    }
}

Produire le résultat

[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 nécessite un cast explicite supplémentaire qui n'est pas requis lors de l'utilisation de mono 2.8.2...

0 votes

Utiliser .Single() pour obtenir l'attribut description n'est pas une bonne idée. S'il n'y a pas d'attribut, Single() lève une exception, SingleOrDefault() ne le fait pas.

0 votes

@kerem bon point, je l'ai mis à jour (en utilisant FirstOrDefault pour éviter de supposer qu'il n'y a qu'un seul attribut). Que cette hypothèse soit ou non une " bonne idée " (ou une " mauvaise idée "), il n'y a pas de raison de ne pas en tenir compte. mauvais d'ailleurs) dépend bien sûr du contexte.

1 votes

J'adore ce système, mais j'ai rencontré un problème : sous Windows 7/.NET 4.5, cette ligne TDerived instance = (TDerived)field.GetValue(null); résulte en instance être null . Il semble que l'ordre d'initialisation des types du runtime Mono soit différent de celui de .NET pour que cela fonctionne. C'est déroutant ! J'ai dû déplacer ce code dans une méthode statique et l'appeler à partir de l'initialisateur de type dans la sous-classe.

5voto

Keith Points 46288

Vous pourriez probablement le faire, mais pas pour l'enum (vous ne pouvez pas y ajouter une méthode). Vous pourriez ajouter une conversion implicite à votre propre classe pour permettre à un enum d'être converti en elle,

public class MyClass {

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

MyClass m = MyEnum.One;

La question est de savoir pourquoi.

En général, .Net évite (et vous devriez aussi) toute conversion implicite où des données peuvent être perdues.

3voto

Ash Points 2002

Si vous définissez la base de l'enum comme un long, vous pouvez effectuer une conversion explicite. Je ne sais pas si vous pouvez utiliser des conversions implicites car les méthodes ne peuvent pas être définies sur les enums.

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

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

De plus, il faut savoir qu'une énumération non capitalisée prendra par défaut la valeur 0, ou le premier élément - donc dans la situation ci-dessus, il serait probablement préférable de définir zero = 0 también.

5 votes

Vous n'avez pas besoin de la : long ici ; la conversion explicite fonctionnerait bien sans elle. La seule conversion implicite légale est le zéro.

0 votes

Je pensais que tous les enums étaient longs par défaut ? Par conséquent, les conversions explicites en longs existent déjà ?

3 votes

Non, l'enum par défaut est Int32

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X