Quelqu'un connaît-il une bonne solution de contournement en cas d'absence de contrainte générique enum ?

StackOverflow https://stackoverflow.com/questions/7244

  •  08-06-2019
  •  | 
  •  

Question

Ce que je veux faire, c'est quelque chose comme ceci :J'ai des énumérations avec des valeurs marquées combinées.

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
        return (input & matchTo) != 0;
    }
}

Alors je pourrais faire :

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

Malheureusement, le générique de C# où les contraintes n'ont aucune restriction d'énumération, seulement une classe et une structure.C# ne voit pas les énumérations comme des structures (même s'il s'agit de types valeur), je ne peux donc pas ajouter de types d'extension comme celui-ci.

Quelqu'un connaît-il une solution de contournement ?

Était-ce utile?

La solution

MODIFIER:Ceci est désormais disponible dans la version 0.0.0.2 de UnconstrainedMelody.

(Comme demandé sur mon article de blog sur les contraintes d'énumération.J'ai inclus les faits de base ci-dessous dans le but d'une réponse autonome.)

La meilleure solution est d'attendre que je l'inclue dans Mélodie sans contrainte1.Il s'agit d'une bibliothèque qui prend du code C# avec de « fausses » contraintes telles que

where T : struct, IEnumConstraint

et le transforme en

where T : struct, System.Enum

via une étape de post-construction.

Ça ne devrait pas être trop difficile à écrire IsSet...bien que répondant aux deux Int64-basé et UInt64-les drapeaux basés sur pourraient être la partie la plus délicate.(Je sens que certaines méthodes d'assistance arrivent, me permettant essentiellement de traiter n'importe quelle énumération de drapeaux comme s'il y avait un type de base de UInt64.)

Quel comportement souhaiteriez-vous adopter si vous appeliez

tester.IsSet(MyFlags.A | MyFlags.C)

?Faut-il vérifier que tous les drapeaux spécifiés sont définis ?Ce serait mon attente.

Je vais essayer de faire ça en rentrant chez moi ce soir...J'espère avoir un éclair rapide sur les méthodes d'énumération utiles pour amener rapidement la bibliothèque à un standard utilisable, puis me détendre un peu.

MODIFIER:je ne suis pas sûr de IsSet comme nom, d'ailleurs.Possibilités :

  • Comprend
  • Contient
  • HasFlag (ou HasFlags)
  • IsSet (c'est certainement une option)

Les pensées sont les bienvenues.Je suis sûr qu'il faudra un certain temps avant que quelque chose ne soit gravé dans le marbre de toute façon...


1 ou soumettez-le sous forme de patch, bien sûr...

Autres conseils

Darren, cela fonctionnerait si les types étaient des énumérations spécifiques - pour que les énumérations générales fonctionnent, vous devez les convertir en ints (ou plus probablement en uint) pour faire le calcul booléen :

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

Depuis C# 7.3, il existe désormais un moyen intégré d'ajouter des contraintes d'énumération :

public class UsingEnum<T> where T : System.Enum { }

source: https://docs.microsoft.com/en-us/dotnet/csharp/langage-reference/keywords/where-generic-type-constraint

En fait, c’est possible, avec une vilaine astuce.Cependant, il ne peut pas être utilisé pour les méthodes d'extension.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.IsSet<DateTimeKind>("Local")

Si tu le veux, tu peux donner Enums<Temp> un constructeur privé et une classe héritée abstraite imbriquée publique avec Temp comme Enum, pour empêcher les versions héritées pour les non-énumérations.

Vous pouvez y parvenir en utilisant IL Weaving et Contraintes supplémentaires

Vous permet d'écrire ce code

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

Ce qui est compilé

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}

Cela ne répond pas à la question initiale, mais il existe désormais une méthode dans .NET 4 appelée Enum.HasFlag qui fait ce que vous essayez de faire dans votre exemple

La façon dont je le fais est de mettre une contrainte de structure, puis de vérifier que T est une énumération au moment de l'exécution.Cela n'élimine pas complètement le problème, mais cela le réduit quelque peu.

Depuis C# 7.3, vous pouvez utiliser la contrainte Enum sur les types génériques :

public static TEnum Parse<TEnum>(string value) where TEnum : Enum
{
    return (TEnum) Enum.Parse(typeof(TEnum), value);
}

Si vous souhaitez utiliser une énumération Nullable, vous devez laisser la contrainte de structure originale :

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}

En utilisant votre code d'origine, dans la méthode, vous pouvez également utiliser la réflexion pour tester que T est une énumération :

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}

Voici un code que je viens de créer et qui semble fonctionner comme vous le souhaitez sans avoir à faire quelque chose de trop fou.Cela ne se limite pas aux énumérations définies comme indicateurs, mais il peut toujours y avoir une vérification si nécessaire.

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}

si quelqu'un a besoin d'un IsSet générique (créé à la volée, qui pourrait être amélioré), et/ou d'une conversion de chaîne en Enum à la volée (qui utilise EnumConstraint présenté ci-dessous) :

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet<TEnum>(this TEnum _this, TEnum flag)
      where TEnum : struct
    {
      return (((uint)Convert.ChangeType(_this, typeof(uint))) & ((uint)Convert.ChangeType(flag, typeof(uint)))) == ((uint)Convert.ChangeType(flag, typeof(uint)));
    }

    //public static TestClass ToTestClass(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestClass>(_this);
    //}

    //public static TestStruct ToTestStruct(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestStruct>(_this);
    //}

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse<TestEnum>(_this);
    }

    public static void TestAll()
    {
      TestEnum t1 = "e3".ToTestEnum();
      TestEnum t2 = "e2".ToTestEnum();
      TestEnum t3 = "non existing".ToTestEnum(); // default(TestEnum) for non existing 

      bool b1 = t3.IsSet(TestEnum.e1); // you can ommit type
      bool b2 = t3.IsSet<TestEnum>(TestEnum.e2); // you can specify explicite type

      TestStruct t;
      // #generates compile error (so no missuse)
      //bool b3 = t.IsSet<TestEnum>(TestEnum.e1);

    }

  }

Si quelqu'un a encore besoin d'un exemple chaud pour créer une contrainte de codage Enum :

using System;

/// <summary>
/// would be same as EnumConstraint_T&lt;Enum>Parse&lt;EnumType>("Normal"),
/// but writen like this it abuses constrain inheritence on System.Enum.
/// </summary>
public class EnumConstraint : EnumConstraint_T<Enum>
{

}

/// <summary>
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// </summary>
/// <typeparam name="TClass">should be System.Enum</typeparam>
public abstract class EnumConstraint_T<TClass>
  where TClass : class
{

  public static TEnum Parse<TEnum>(string value)
    where TEnum : TClass
  {
    return (TEnum)Enum.Parse(typeof(TEnum), value);
  }

  public static bool TryParse<TEnum>(string value, out TEnum evalue)
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    evalue = default(TEnum);
    return Enum.TryParse<TEnum>(value, out evalue);
  }

  public static TEnum TryParse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {    
    Enum.TryParse<TEnum>(value, out defaultValue);
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    TEnum result;
    if (Enum.TryParse<TEnum>(value, out result))
      return result;
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(ushort value)
  {
    return (TEnum)(object)value;
  }

  public static sbyte to_i1<TEnum>(TEnum value)
  {
    return (sbyte)(object)Convert.ChangeType(value, typeof(sbyte));
  }

  public static byte to_u1<TEnum>(TEnum value)
  {
    return (byte)(object)Convert.ChangeType(value, typeof(byte));
  }

  public static short to_i2<TEnum>(TEnum value)
  {
    return (short)(object)Convert.ChangeType(value, typeof(short));
  }

  public static ushort to_u2<TEnum>(TEnum value)
  {
    return (ushort)(object)Convert.ChangeType(value, typeof(ushort));
  }

  public static int to_i4<TEnum>(TEnum value)
  {
    return (int)(object)Convert.ChangeType(value, typeof(int));
  }

  public static uint to_u4<TEnum>(TEnum value)
  {
    return (uint)(object)Convert.ChangeType(value, typeof(uint));
  }

}

j'espère que cela aide quelqu'un.

Je voulais juste ajouter Enum comme contrainte générique.

Bien qu'il ne s'agisse que d'une petite méthode d'assistance utilisant ExtraConstraints c'est un peu trop de frais généraux pour moi.

J'ai décidé de simplement créer un struct contrainte et ajouter une vérification d'exécution pour IsEnum.Pour convertir une variable de T en Enum, je la convertis d'abord en objet.

    public static Converter<T, string> CreateConverter<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter<T, string>(x => ((Enum)(object)x).GetEnumDescription());
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top