Qualcuno conosce una buona soluzione alternativa per la mancanza di un vincolo generico enum?


89

Quello che voglio fare è qualcosa del genere: ho enumerazioni con valori contrassegnati combinati.

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;
    }
}

Quindi potrei fare:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Sfortunatamente, il generico di C # in cui i vincoli non hanno restrizioni di enum, solo classe e struttura. C # non vede le enumerazioni come strutture (anche se sono tipi di valore), quindi non posso aggiungere tipi di estensione come questo.

Qualcuno conosce una soluzione alternativa?


2
Keith: scarica la versione 0.0.0.2 di UnconstrainedMelody - Ho implementato HasAll e HasAny. Godere.
Jon Skeet,

Cosa intendi con "C # non vede le enumerazioni come strutture"? È possibile utilizzare i tipi di enumerazione come parametri di tipo che sono limitati structcorrettamente.
Timwi

controlla questo articolo qui: codeproject.com/KB/cs/ExtendEnum.aspx "IsValidEnumValue" o "IsFlagsEnumDefined" sono probabilmente la risposta alla tua domanda.
dmihailescu

1
Vota per questa idea di uservoice , se vuoi vederla incorporata in .net un giorno.
Matthieu

11
C # 7.3 introduce i vincoli enum.
Marc Sigrist

Risposte:


48

EDIT: ora è disponibile nella versione 0.0.0.2 di UnconstrainedMelody.

(Come richiesto nel mio post sul blog sui vincoli enum . Ho incluso i fatti di base di seguito per il bene di una risposta autonoma.)

La soluzione migliore è aspettare che lo includa in UnconstrainedMelody 1 . Questa è una libreria che accetta codice C # con vincoli "falsi" come

where T : struct, IEnumConstraint

e lo trasforma in

where T : struct, System.Enum

tramite una fase di post-costruzione.

Non dovrebbe essere troppo difficile da scrivere IsSet... anche se ristorazione sia per Int64basati e UInt64bandiere basati potrebbe essere la parte difficile. (Sento l'odore di alcuni metodi di supporto che mi permettono di trattare qualsiasi enum di flag come se avesse un tipo base di UInt64.)

Come vorresti che fosse il comportamento se chiamassi

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

? Dovrebbe controllare che tutti i flag specificati siano impostati? Questa sarebbe la mia aspettativa.

Proverò a farlo mentre torni a casa stasera ... Spero di avere un rapido blitz su utili metodi di enumerazione per portare rapidamente la libreria a uno standard utilizzabile, quindi rilassarmi un po '.

EDIT: non sono sicuro di IsSetcome nome, a proposito. Opzioni:

  • Include
  • Contiene
  • HasFlag (o HasFlags)
  • IsSet (è sicuramente un'opzione)

Pensieri benvenuti. Sono sicuro che ci vorrà del tempo prima che qualcosa sia scolpito nella pietra comunque ...


1 o inviarlo come patch, ovviamente ...


1
Dovevi andare a menzionare PostSharp LOL: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell,

1
O in realtà più semplici HasAny () e HasAll ()
Keith

1
Sì, sono d'accordo che è anche meglio. colors.HasAny(Colors.Red | Colors.Blue)sembra un codice molto leggibile. =)
Blixt

1
Sì, mi piacciono anche HasAny e HasAll. Andrà con quello.
Jon Skeet,

5
A partire da C # 7.3 (rilasciato a maggio 2018), è possibile utilizzare il vincolo where T : System.Enum. Questo è stato già scritto altrove nel thread; ho solo pensato di ripeterlo qui.
Jeppe Stig Nielsen


16

Darren, ciò funzionerebbe se i tipi fossero enumerazioni specifiche - affinché le enumerazioni generali funzionino devi convertirle in int (o più probabilmente uint) per eseguire i calcoli booleani:

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

1
E se hai un numero ridicolo di flag, puoi chiamare GetTypeCode () sugli argomenti e Convert.ToUint64 ()
Kit

Fantastico, la combinazione di "Enum" e Convert.ToUInt32non ho trovato da nessun'altra parte. Per quanto ne so, è l'unica soluzione Pre-Net-4 decente che funziona anche in VB. A proposito, se matchTopotrebbe avere più bit di flag, quindi sostituire != 0con == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
Si noti che Convert.ToUInt32utilizzato con un'enumerazione utilizzerà l' Convert.ToUInt32(object)overload, il che significa che CLR prima boxerterà questi valori prima di passare quindi al ToUInt32metodo. Nella maggior parte dei casi questo non avrà importanza, ma è bene sapere che manterrai il GC piuttosto occupato se stai usando qualcosa di simile per analizzare milioni di enumerazioni al secondo.
Groo

10

In realtà, è possibile, con un brutto trucco. Tuttavia, non può essere utilizzato per i metodi di estensione.

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")

Se lo desideri, puoi fornire Enums<Temp>un costruttore privato e una classe ereditata astratta annidata pubblica con Tempas Enum, per evitare versioni ereditate per non enumerazioni.


8

È possibile ottenere ciò utilizzando IL Weaving e ExtraConstraints

Ti permette di scrivere questo codice

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

Cosa viene compilato

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

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

6

A partire da C # 7.3, è possibile utilizzare il vincolo Enum sui tipi generici:

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

Se vuoi usare un'enumerazione nullable, devi lasciare il vincolo della struttura originale:

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

4

Questo non risponde alla domanda originale, ma ora esiste un metodo in .NET 4 chiamato Enum.HasFlag che fa quello che stai cercando di fare nel tuo esempio


Votato perché a questo punto, quasi tutti dovrebbero utilizzare .NET 4 (o versioni successive) e quindi dovrebbero utilizzare questo metodo invece di tentare di hackerarlo insieme.
CptRobby

Votato. Tuttavia la loro soluzione utilizza la boxe dell'argomento flag. .NET 4.0 compie cinque anni ormai.
Jeppe Stig Nielsen

3

Il modo in cui lo faccio è mettere un vincolo struct, quindi verificare che T sia un enum in fase di esecuzione. Questo non elimina completamente il problema, ma lo riduce in qualche modo


7
dove T: struct, IComparable, IFormattable, IConvertible - questo è il più vicino possibile all'enumerazione :)
Kit

1

Usando il tuo codice originale, all'interno del metodo puoi anche usare la reflection per verificare che T sia un'enumerazione:

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;
    }
}

2
Grazie, ma questo trasforma un problema di compilazione (il vincolo where) in uno di runtime (la tua eccezione). Inoltre avresti ancora bisogno di convertire gli input in int prima di poter fare qualsiasi cosa con loro.
Keith

1

Ecco un po 'di codice che ho appena creato che sembra funzionare come vuoi senza dover fare nulla di troppo folle. Non è limitato alle sole enumerazioni impostate come Flag, ma potrebbe sempre essere inserito un controllo se necessario.

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

se qualcuno ha bisogno di un IsSet generico (creato fuori dalla scatola al volo potrebbe essere migliorato) e / o stringa per la conversione di Enum onfly (che utilizza EnumConstraint presentato di seguito):

  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);

    }

  }

Se qualcuno ha ancora bisogno di un esempio per creare il vincolo di codifica 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));
  }

}

spero che questo aiuti qualcuno.


0

Volevo solo aggiungere Enum come vincolo generico.

Anche se questo è solo per un piccolo metodo di supporto, l'utilizzo ExtraConstraintsè un po 'troppo sovraccarico per me.

Ho deciso di creare solo un structvincolo e aggiungere un controllo di runtime per IsEnum. Per convertire una variabile da T a Enum, la lancio prima in object.

    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());
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.