using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Developpez.Dotnet.Properties;
using JetBrains.Annotations;
using CH = Developpez.Dotnet.CompatibilityHelper;
#if !NETFX_CORE
using System.ComponentModel;
using Developpez.Dotnet.Collections;
#endif
namespace Developpez.Dotnet
{
///
/// Fournit des méthodes d'extension pour manipuler les énumérations
///
public static class EnumExtensions
{
///
/// Cette classe permet de mettre en cache les informations d'un type
/// d'énumération en particulier. Les informations ne sont évaluées
/// qu'une fois par type, dans le constructeur statique
///
/// Le type d'énumération
private static class EnumInfoCache
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
// ReSharper disable StaticFieldInGenericType
private static readonly bool _isEnum;
private static readonly bool _hasFlags;
private static readonly Type _underlyingType;
private static readonly T[] _values;
private static readonly T[] _flagValues;
#if !NETFX_CORE
private static readonly IDictionary _descriptions;
#endif
public static readonly Func And;
public static readonly Func Or;
public static readonly Func Not;
// ReSharper restore StaticFieldInGenericType
static EnumInfoCache()
{
#if NETFX_CORE
_isEnum = typeof(T).GetTypeInfo().IsEnum;
#else
_isEnum = typeof(T).IsEnum;
#endif
if (_isEnum)
{
_underlyingType = Enum.GetUnderlyingType(typeof(T));
_values = CH.Enum.GetValues(typeof(T)).Cast().ToArray();
_flagValues = _values.Where(x => IsPowerOfTwo(Convert.ToUInt64(x))).ToArray();
#if NETFX_CORE
_hasFlags = typeof (T).GetTypeInfo().IsDefined(typeof(FlagsAttribute));
#else
_hasFlags = Attribute.IsDefined(typeof(T), typeof(FlagsAttribute));
_descriptions = new DefaultDictionary(false);
foreach (var v in _values)
{
_descriptions.Add(v, GetDescription(v));
}
#endif
And = CreateFunc(Expression.And);
Or = CreateFunc(Expression.Or);
Not = CreateFunc(Expression.Not);
}
}
private static Func CreateFunc(Func expressionFactory)
{
var arg = Expression.Parameter(typeof(T), "x");
var castArg = Expression.Convert(arg, _underlyingType);
var body = expressionFactory(castArg);
var castBody = Expression.Convert(body, typeof(T));
var expr = Expression.Lambda>(castBody, arg);
return expr.Compile();
}
private static Func CreateFunc(Func expressionFactory)
{
var left = Expression.Parameter(typeof(T), "x");
var right = Expression.Parameter(typeof(T), "y");
var castLeft = Expression.Convert(left, _underlyingType);
var castRight = Expression.Convert(right, _underlyingType);
var body = expressionFactory(castLeft, castRight);
var castBody = Expression.Convert(body, typeof(T));
var expr = Expression.Lambda>(castBody, left, right);
return expr.Compile();
}
public static bool IsEnum
{
get { return _isEnum; }
}
public static bool HasFlags
{
get { return _hasFlags; }
}
public static IEnumerable Values
{
get { return _values; }
}
public static IEnumerable FlagValues
{
get { return _flagValues; }
}
#if !NETFX_CORE
public static IDictionary Descriptions
{
get { return _descriptions; }
}
private static string GetDescription(T value)
{
Type type = typeof(T);
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attr != null)
{
return attr.Description;
}
}
}
return null;
}
#endif
private static bool IsPowerOfTwo(ulong x)
{
return (x != 0) && (x & (x - 1)) == 0;
}
}
///
/// Vérifie que le type spécifié est une énumération, et optionnellement s'il a l'attribut 'Flags'.
/// Si ce n'est pas le cas, une exception est levée.
///
/// indique si le type à vérifier doit porter l'attribut Flags
/// Le type à vérifier
[AssertionMethod]
private static void CheckIsEnum(bool withFlags)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
if (!EnumInfoCache.IsEnum)
throw new ArgumentException(string.Format(ExceptionMessages.TypeIsNotEnum, typeof(T).FullName));
if (withFlags && !EnumInfoCache.HasFlags)
throw new ArgumentException(string.Format(ExceptionMessages.TypeNotDecoratedWithFlags, typeof(T).FullName));
}
///
/// Vérifie si le flag spécifié est présent dans la valeur
///
/// Le type de l'énumération
/// La combinaison de flags à vérifier
/// Le flag à vérifier
/// true si le flag est présent, false sinon
public static bool HasFlag(this T value, T flag)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(true);
return !EnumInfoCache.And(value, flag).Equals(default(T));
}
///
/// Convertit une combinaison de flags en une liste de valeurs de l'énumération, en
/// excluant les combinaisons prédéfinies.
///
/// Le type de l'énumération
/// La combinaison de flags à convertir
/// La liste des flags présents dans la combinaison
public static IEnumerable GetFlags(this T value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(true);
return value.GetFlagsIterator(true);
}
///
/// Convertit une combinaison de flags en une liste de valeurs de l'énumération, en
/// excluant optionnellement les combinaisons prédéfinies.
///
/// Le type de l'énumération
/// La combinaison de flags à convertir
/// Indique si seuls les flags simples sont renvoyés, en excluant les combinaisons connues.
/// La liste des flags présents dans la combinaison
public static IEnumerable GetFlags(this T value, bool flagsOnly)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(true);
return value.GetFlagsIterator(flagsOnly);
}
private static IEnumerable GetFlagsIterator(this T value, bool flagsOnly)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
var values = flagsOnly
? EnumInfoCache.FlagValues
: EnumInfoCache.Values;
foreach (T flag in values)
{
if (value.HasFlag(flag))
yield return flag;
}
}
///
/// Définit la valeur d'un ou des flags dans une combinaison de flags
///
/// Le type de l'énumération
/// La combinaison de flags à modifier
/// Le ou les flags à définir
/// true pour ajouter le flag, false pour l'enlever
/// La combinaison résultant de l'ajout ou de la suppression des flags
public static T SetFlags(this T value, T flags, bool on)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(true);
if (on)
return EnumInfoCache.Or(value, flags);
return EnumInfoCache.And(value, EnumInfoCache.Not(flags));
}
///
/// Ajoute un ou des flags à une combinaison
///
/// Le type de l'énumération
/// La combinaison de flags à modifier
/// Le ou les flags à ajouter
/// La combinaison résultant de l'ajout des flags
public static T SetFlags(this T value, T flags)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
return value.SetFlags(flags, true);
}
///
/// Supprime un ou des flags d'une combinaison
///
/// Le type de l'énumération
/// La combinaison de flags à modifier
/// Le ou les flags à supprimer
/// La combinaison résultant de la suppression des flags
public static T ClearFlags(this T value, T flags)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
return value.SetFlags(flags, false);
}
///
/// Convertit une liste de flags en une combinaison de ces flags
///
/// Le type de l'énumération
/// La liste de flags à combiner
/// La combinaison des flags de la liste
public static T CombineFlags(this IEnumerable flags)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(true);
T tmp = default(T);
foreach (T flag in flags)
{
tmp = EnumInfoCache.Or(tmp, flag);
}
return tmp;
}
#if !NETFX_CORE
///
/// Renvoie, si elle est définie, la description d'une valeur d'une énumération, spécifiée par
/// l'attribut DescriptionAttribute. Si la description n'est pas définie, renvoie null.
///
/// Le type de l'énumération
/// la valeur pour laquelle obtenir une description
/// La description de la valeur si elle est définie, null sinon.
public static string GetDescription(this T value)
where T : struct, IComparable, IFormattable, IConvertible
{
CheckIsEnum(false);
return EnumInfoCache.Descriptions[value];
}
#endif
///
/// Convertit une chaine en une valeur d'énumération, en ignorant éventuellement la casse
///
/// Type d'énumération souhaité
/// Chaine à convertir
/// true pour ignorer la casse, false sinon
/// La valeur d'énumération correspondant à la chaine
public static T ToEnum(this string stringValue, bool ignoreCase)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.Parse(typeof(T), stringValue, ignoreCase);
}
///
/// Convertit une chaine en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Chaine à convertir
/// La valeur d'énumération correspondant à la chaine
public static T ToEnum(this string stringValue)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
return stringValue.ToEnum(false);
}
///
/// Convertit un entier signé sur 8 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this sbyte value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier signé sur 16 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this short value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier signé sur 32 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this int value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier signé sur 64 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this long value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier non signé sur 8 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this byte value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier non signé sur 16 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this ushort value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier non signé sur 32 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this uint value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
///
/// Convertit un entier non signé sur 64 bits en une valeur d'énumération
///
/// Type d'énumération souhaité
/// Valeur à convertir
/// La valeur d'énumération correspondant à la valeur
public static T ToEnum(this ulong value)
#if NETFX_CORE
where T : struct, IComparable, IFormattable
#else
where T : struct, IComparable, IFormattable, IConvertible
#endif
{
CheckIsEnum(false);
return (T)Enum.ToObject(typeof(T), value);
}
}
}