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 ?
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 ?
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;
}
}
}
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) ;
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.
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.
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
@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.
J'ai adapté l'excellente classe de base générique RichEnum de Mark.
Correction de
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...
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.
@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.
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.
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.
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.
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.
Je pensais que tous les enums étaient longs par défaut ? Par conséquent, les conversions explicites en longs existent déjà ?
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.
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 ?