43 votes

Méthode d'extension générique pour voir si un enum contient un drapeau

En considérant ceci :

[Flags]
public enum MyEnum {
    One = 1,
    Two = 2,
    Four = 4,
    Eight = 8
}

public static class FlagsHelper
{
    public static bool Contains(this MyEnum keys, MyEnum flag)
    {
        return (keys & flag) != 0;
    }
}

Est-il possible d'écrire une version générique de Contains qui fonctionnerait pour n'importe quelle enum et pas seulement MyEnum ?

Editar:

Ce serait ma version après avoir lu vos réponses :

    public static bool Contains(this Enum keys, Enum flag)
    {
        ulong keysVal = Convert.ToUInt64(keys);
        ulong flagVal = Convert.ToUInt64(flag);

        return (keysVal & flagVal) == flagVal;
    }

Je viens de réaliser que c'est une mauvaise idée de vérifier la façon dont je vérifiais ( return (keys & flag) != 0; ), car le flag peut être en fait plusieurs drapeaux et la chose la plus sensée à faire est de retourner vrai seulement si keys contient tous ces éléments. De plus, je ne vérifierais pas les valeurs nulles ni même s'ils sont du même type. Je pourrais veulent pour utiliser différents types.

57voto

chilltemp Points 3777

J'ai basé cette méthode sur un tas de recherches dans SO et Google, et en utilisant reflector pour voir ce que MS a fait pour la méthode HasFlags de .NET 4.

public static class EnumExt
{
    /// <summary>
    /// Check to see if a flags enumeration has a specific flag set.
    /// </summary>
    /// <param name="variable">Flags enumeration to check</param>
    /// <param name="value">Flag to check for</param>
    /// <returns></returns>
    public static bool HasFlag(this Enum variable, Enum value)
    {
        if (variable == null)
            return false;

        if (value == null)
            throw new ArgumentNullException("value");

        // Not as good as the .NET 4 version of this function, but should be good enough
        if (!Enum.IsDefined(variable.GetType(), value))
        {
            throw new ArgumentException(string.Format(
                "Enumeration type mismatch.  The flag is of type '{0}', was expecting '{1}'.",
                value.GetType(), variable.GetType()));
        }

        ulong num = Convert.ToUInt64(value);
        return ((Convert.ToUInt64(variable) & num) == num);

    }

}

Notes :

  • Cela permet de gérer les valeurs nulles
  • Effectue une vérification du type
  • Convertit en un ulong, et peut gérer n'importe quelle valeur positive de l'enum. Microsoft met en garde contre l'utilisation d'énumérations de drapeaux négatifs de toute façon :

    Soyez prudent si vous définissez un nombre négatif en tant que constante énumérée de drapeau car de nombreuses positions de drapeau peuvent être mises à 1, ce qui peut rendre votre code confus et encourager les erreurs de codage.

7voto

Justin Niessner Points 144953

Je ne sais pas si vous utilisez .NET 4.0 ou non, mais il est livré avec la méthode statique Enum.HasFlags() .

-- Code supprimé (la solution acceptée l'a déjà fait) --

5voto

Michael B Points 3222

Voici mon approche : elle est sûre pour le type et ne fait pas de boxing ou de unboxing. Elle lève une exception si le type n'est pas un enum. Il y a une technique que vous pouvez utiliser si vous voulez le transformer en une méthode statique publique qui sera typée en Enum, mais il ne peut pas être une méthode d'extension alors. Il n'y a pas non plus besoin de vérifier la nullité, car la contrainte de la structure bloque également les enum nullables. Je ne pense pas qu'il y ait beaucoup de choses à faire pour améliorer ce code, à l'exception peut-être de l'écrire en F# ou C++/CLI afin de pouvoir y mettre une contrainte d'enum. L'idée est de construire une fonction utilisant des arbres d'expression qui convertira l'enum en long s'il s'agit d'autre chose qu'un enum basé sur un ulong, ou en ulong et ensuite et eux, produisant essentiellement : : return value & flag == flag

public static class EnumExtensions
 {
  #region Public Static Methods 
  /// <summary>
  /// Determines whether the specified value has flags. Note this method is up to 60 times faster
  /// than the one that comes with .NET 4 as it avoids any explict boxing or unboxing. 
  /// </summary>
  /// <typeparam name="TEnum">The type of the enum.</typeparam>
  /// <param name="value">The value.</param>
  /// <param name="flag">The flag.</param>
  /// <returns>
  ///  <c>true</c> if the specified value has flags; otherwise, <c>false</c>.
  /// </returns>
  /// <exception cref="ArgumentException">If TEnum is not an enum.</exception>
  public static bool HasFlags<TEnum>(this TEnum value, TEnum flag) where TEnum:struct,IComparable,IConvertible,IFormattable
  {
   return EnumExtensionsInternal<TEnum>.HasFlagsDelegate(value, flag);
  }
  #endregion Public Static Methods 

  #region Nested Classes 

  static class EnumExtensionsInternal<TEnum> where TEnum : struct,IComparable, IConvertible, IFormattable
  {
  #region Public Static Variables 
   /// <summary>
   /// The delegate which determines if a flag is set.
   /// </summary>
   public static readonly Func<TEnum, TEnum, bool> HasFlagsDelegate = CreateHasFlagDelegate();
  #endregion Public Static Variables 

  #region Private Static Methods 
   /// <summary>
   /// Creates the has flag delegate.
   /// </summary>
   /// <returns></returns>
   private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate()
   {
    if(!typeof(TEnum).IsEnum)
    {
     throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
    }
    ParameterExpression valueExpression = Expression.Parameter(typeof(TEnum));
    ParameterExpression flagExpression = Expression.Parameter(typeof(TEnum));
    ParameterExpression flagValueVariable = Expression.Variable(Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long));
    Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
      Expression.Block(
        new[] { flagValueVariable },
        Expression.Assign(
          flagValueVariable,
          Expression.Convert(
            flagExpression,
            flagValueVariable.Type
          )
        ),
        Expression.Equal(
          Expression.And(
            Expression.Convert(
              valueExpression,
              flagValueVariable.Type
            ),
            flagValueVariable
          ),
          flagValueVariable
        )
      ),
      valueExpression,
      flagExpression
    );
    return lambdaExpression.Compile();
   }
  #endregion Private Static Methods 
  }
  #endregion Nested Classes 
 }

Comme j'ai oublié que l'arbre d'expression ci-dessus n'est que .NET 4, la méthode suivante devrait fonctionner en .NET 3.5 pour créer le même arbre d'expression: :

        private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate2()
        {
            if(!typeof(TEnum).IsEnum)
            {
                throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name);
            }
            ParameterExpression valueExpression = Expression.Parameter(
                    typeof(TEnum),
                    typeof(TEnum).Name
            );
            ParameterExpression flagExpression = Expression.Parameter(
                    typeof(TEnum),
                    typeof(TEnum).Name
            );
            var targetType = Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long);
            Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
                            Expression.Equal(
                                    Expression.And(
                                            Expression.Convert(
                                                    valueExpression,
                                                    targetType
                                            ),
                                            Expression.Convert(
                                                flagExpression,
                                                targetType
                                            )
                                    ),
                                    Expression.Convert(
                                        flagExpression,
                                        targetType
                                    )
                            ),
                    valueExpression,
                    flagExpression
            );
            return lambdaExpression.Compile();
        }

cette version devrait compiler en .NET 3.5 et si ce n'est pas le cas, je ne comprends pas pourquoi.

3voto

JaredPar Points 333733

Malheureusement, il n'y a pas de bonne façon de créer une méthode d'extension comme celle-ci. Pour que cela fonctionne, il faudrait une méthode générique qui opère sur les éléments suivants enum valeurs. Malheureusement, il n'existe aucun moyen de contraindre les arguments génériques à être un enum.

// Ilegal
public static bool Contains<T>(this T value, T flag) where T : enum {
  ...
}

La meilleure solution que j'ai trouvée est la suivante

public static bool HasFlag<T>(this System.Enum e, T flag) 
{
    var intValue = (int)(object)e;
    var intFlag = (int)(object)flag;
    return (intValue & intFlag) != 0;
}

Cependant, il est limité à plusieurs égards

  • Pas de sécurité de type car il n'est pas nécessaire que la valeur et le drapeau aient le même type.
  • On suppose que tous les enum Les valeurs sont int basé.
  • Provoque une mise en boîte pour un simple contrôle de bit.
  • Sera lancé si e es null

2voto

CodingWithSpike Points 17720

Vous pouvez essentiellement utiliser votre méthode d'extension existante, en utilisant la fonction Enum au lieu de MyEnum . Le problème est qu'il ne sait pas que les enums sont des drapeaux et qu'il ne permet pas à l'utilisateur d'accéder à l'enumération. & Il suffit donc de convertir les valeurs de l'enum en chiffres.

    public static bool Contains(this Enum keys, Enum flag)
    {
        if (keys.GetType() != flag.GetType())
            throw new ArgumentException("Type Mismatch");
        return (Convert.ToUInt64(keys) & Convert.ToUInt64(flag)) != 0;
    }

Et un test unitaire pour faire bonne mesure :

    [TestMethod]
    public void TestContains()
    {
        var e1 = MyEnum.One | MyEnum.Two;
        Assert.IsTrue( e1.Contains(MyEnum.Two) );

        var e2 = MyEnum.One | MyEnum.Four;
        Assert.IsFalse(e2.Contains(MyEnum.Two));
    }

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