Oricine știe o soluție bună pentru lipsa unei constrângeri generice enum?

Ceea ce vreau să fac este ceva de genul acesta: am enums cu valori combinate marcate.

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

Deci aș putea să fac:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Din păcate, C# generic în cazul în care constrângerile nu au nici o restricție enum, doar clasa și struct. C# nu vede enums ca structuri (chiar dacă acestea sunt tipuri de valoare), deci nu pot adăuga tipuri de extensii ca acesta.

Oricine știe o soluție?

0
fr hi bn
@dmihailescu - a se vedea @ Jon Skeet soluția mult mai completă și detaliată în răspunsul acceptat.
adăugat autor Keith, sursa
Ce vrei sa spui prin? C# nu vede enums ca structuri ?? Puteți folosi tipurile enum ca parametri de tip care sunt constrânși doar la struct .
adăugat autor Timwi, sursa
Keith: versiunea de descărcare 0.0.0.2 a UnconstrainedMelody - Am implementat HasAll și HasAny. Bucurați-vă.
adăugat autor Jon Skeet, sursa
Votează pentru idee uservoice >, dacă doriți să îl vedeți încorporat în .net într-o zi.
adăugat autor Matthieu, sursa
consultați acest articol aici: codeproject.com/KB/cs/ExtendEnum.aspx Metodele "IsValidEnumValue" sau "IsFlagsEnumDefined" sunt probabil răspunsul la întrebarea dvs.
adăugat autor dmihailescu, sursa
@dmihailescu: Nu, proiectul de cod se adresează unei probleme mai complexe: determinarea dacă o valoare dată este o valoare validă pentru un Enum dat. De exemplu. dacă vi se dă o valoare "9", este valabil pentru Enum? (Nu dacă aveți doar 3 biți de pavilion). OP încearcă să generalizeze operațiunile de biți simpli pe un flags enum. Aceste operațiuni sunt de tip one-liners, dacă aveți un tip SPECIFIC Enum. De exemplu. ștergerea unui bit de pavilion este ceva de genul value = value & (~ MyEnum.FlagA) . Dar durerea lui trebuie să scrie de fiecare dată, așa că ar fi frumos să
adăugat autor ToolmakerSteve, sursa
C# 7.3 introduce constrângeri enum.
adăugat autor Marc Sigrist, sursa

11 răspunsuri

Darren, asta ar funcționa dacă tipurile ar fi enumerări specifice - pentru ca enumerările generale să funcționeze, trebuie să le aruncați la ints (sau mai probabil uint) pentru a face matematica booleană:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
0
adăugat
Rețineți că Convert.ToUInt32 utilizat cu un enum va utiliza suprasarcina Convert.ToUInt32 (object) , ceea ce înseamnă că CLR va bifa mai întâi aceste valori înainte de a trece apoi la ToUInt32 . În cele mai multe cazuri acest lucru nu contează, dar este bine să știți că veți păstra GC destul de ocupat dacă utilizați ceva de genul acesta pentru a analiza milioane de enumuri pe secundă.
adăugat autor Groo, sursa
Și dacă aveți un număr ridicol de steaguri, puteți apela GetTypeCode() pe argumentele și Convert.ToUint64 ()
adăugat autor Kit, sursa
Minunat, combinația dintre 'Enum` și Convert.ToUInt32 nu am găsit în altă parte. AFAIK, este singura soluție decentă Pre-Net-4 care funcționează și în VB. BTW, dacă matchTo ar putea avea mai mulți biți de pavilion, apoi înlocuiți ! = 0 cu == Convert.ToUInt32 (matchTo) .
adăugat autor ToolmakerSteve, sursa

Modul în care o fac este să pună o constrângere structură, apoi să verifici dacă T este un enum în timpul execuției. Acest lucru nu elimină complet problema, dar o reduce într-o oarecare măsură

0
adăugat
unde T: struct, IComparabil, IFormattable, IConvertible - aceasta este cea mai apropiată puteți obține la enum :)
adăugat autor Kit, sursa

EDIT: Acest lucru este live în versiunea 0.0.0.2 a aplicației UnconstrainedMelody.

(Așa cum am solicitat în post de blog despre enum constrângeri . Am inclus faptele de bază de mai jos pentru un răspuns independent.)

Cea mai bună soluție este să așteptați să o includ în UnconstrainedMelody 1 . Aceasta este o bibliotecă care ia codul C# cu constrângeri "false" cum ar fi

where T : struct, IEnumConstraint

și o transformă

where T : struct, System.Enum

printr-un pas postbuild.

Nu ar trebui să fie prea greu să scrieți IsSet ... deși alimentarea atît pentru codurile Int64 , cât și pentru UInt64 parte. (Mi se pare că mi-a venit câteva metode de ajutor, în principiu mi-a permis să tratez orice enum de steaguri ca și cum ar avea un tip de bază UInt64 ).

Ce vrei să fie comportamentul dacă ai sunat

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

? Ar trebui să verifice dacă toate sunt setate steagurile specificate? Asta ar fi așteptarea mea.

Voi incerca sa fac asta in drum spre casa in seara asta ... Sper sa fiu un blitz rapid pe metode utile enum pentru a obtine rapid biblioteca la un standard utilizabil, apoi relaxati-va un pic.

EDIT: Nu sunt sigur despre IsSet ca un nume, apropo. Opțiuni:

  • Include
  • Conține
  • HasFlag (sau HasFlags)
  • IsSet (este cu siguranță o opțiune)

Gândurile sunt binevenite. Sunt sigur că va mai fi ceva timp înainte ca orice să fie pus în piatră oricum ...


1 or submit it as a patch, of course...

0
adăugat
Presupun că în cazul în care mai multe steaguri sunt trecute în ea ar trebui să verifice pentru toate acestea. Fixul meu real pentru acest lucru (din 2008 când l-am întrebat) a fost să aibă o metodă de extindere a șablonului pentru fiecare flags enum - messy, dar funcționează. Nu am deranjat niciodată controlul pentru mai multe steaguri, deoarece toate verificările pe care le avem sunt pentru un singur drapel - nu o astfel de problemă în codul intern, ci ceva ce ar trebui să fie contabilizat într-o bibliotecă partajată.
adăugat autor Keith, sursa
Sau, de fapt, mai simplu HasAny() și HasAll ()
adăugat autor Keith, sursa
Când am început să citesc partea de sus a răspunsului, am început să-mi spun: "O altă bibliotecă? Șurubați asta, dacă nu a fost scrisă de Jon Skeet". Apoi am coborât și am fost răsplătit.
adăugat autor krillgar, sursa
adăugat autor Sam Harwell, sursa
actualizați linkul din partea de sus: postarea de blog despre constrângerile enum
adăugat autor Zinov, sursa
Da, imi plac si HasAny si HasAll. Va merge cu asta.
adăugat autor Jon Skeet, sursa
HasAny și HasAll părea minunat.
adăugat autor IDisposable, sursa
Da, sunt de acord că este chiar mai bine. colors.HasAny (Colors.Red | Colors.Blue) arată ca un cod foarte ușor de citit. =)
adăugat autor Blixt, sursa
Oh și Keith, aruncați o privire la stackoverflow.com/questions/1404077/… =)
adăugat autor Blixt, sursa
Voi folosi terminologia Flags pentru că există deja în .NET (vezi FlagsAttribute ). Aici văd două nume explicite: HasAnyFlag și HasAllFlags . Acestea pot fi reduse la HasFlag și HasFlags . Nu pot spune ce este mai bun, este o chestie de gust.
adăugat autor Blixt, sursa

Vroiam doar să adaug Enum ca pe o constrângere generică.

În timp ce acest lucru este doar pentru o mică metodă de ajutor folosind ExtraConstraints este un prea mult pentru mine.

Am decis doar să creez o constrângere struct și să adaug un check runtime pentru IsEnum . Pentru a converti o variabilă de la T la Enum, l-am aruncat mai întâi la obiect.

    public static Converter CreateConverter() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter(x => ((Enum)(object)x).GetEnumDescription());
    }
0
adăugat

You can achieve this using IL Weaving and ExtraConstraints

Vă permite să scrieți acest cod

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

Ce se compilează

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

    public void MethodWithEnumConstraint() where T: struct, Enum
    {
    }
}
0
adăugat

De fapt, este posibil, cu un truc urât. Cu toate acestea, nu poate fi folosit pentru metode de extensie.

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

Enums.IsSet("Local")

If you want to, you can give Enums a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

0
adăugat

Folosind codul original, în interiorul metodei puteți folosi și reflecția pentru a testa că T este un enum:

public static class EnumExtension
{
    public static bool IsSet( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}
0
adăugat
Multumesc, dar care transformă o problemă de timp de compilare (unde constrângerea) într-un runtime unul (excepția dvs.). De asemenea, va trebui în continuare să transformați intrările în inturi înainte de a putea face ceva cu ele.
adăugat autor Keith, sursa

Acest lucru nu răspunde la întrebarea inițială, dar acum există o metodă în .NET 4 numită Enum.HasFlag care face ceea ce încerci să faci în exemplul tău

0
adăugat
Upvoted. Cu toate acestea, soluția lor utilizează boxul argumentului . .NET 4.0 are acum cinci ani.
adăugat autor Jeppe Stig Nielsen, sursa
Upvoted deoarece, în acest moment, majoritatea ar trebui să folosească .NET 4 (sau mai mult) și astfel ar trebui să folosească această metodă în loc să încerce să o hack împreună.
adăugat autor CptRobby, sursa

Iată câteva coduri pe care tocmai le-am făcut, lucru care pare să funcționeze așa cum doriți, fără a fi nevoie să faceți ceva prea nebun. Nu se limitează numai la enums setat ca Steaguri, dar ar putea fi întotdeauna verificat dacă este necesar.

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.");
    }
}
0
adăugat

dacă cineva are nevoie de generic IsSet (creat din cutie în zbor poate fi îmbunătățit) și sau șir la conversia Enum onfly (care utilizează EnumConstraint prezentat mai jos):

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet(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(_this);
    //}

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

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse(_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.e2); // you can specify explicite type

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

    }

  }

Dacă cineva are nevoie de un exemplu fierbinte pentru a crea constrângere de codare Enum:

using System;

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

}

/// 
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// 
/// should be System.Enum
public abstract class EnumConstraint_T
  where TClass : class
{

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

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

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

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

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

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

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

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

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

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

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

}

Sper că acest lucru îi ajută pe cineva.

0
adăugat

Începând cu C# 7.3, există acum un mod încorporat de a adăuga constrângeri enum:

public class UsingEnum where T : System.Enum { }

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

0
adăugat