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

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

  •  08-06-2019
  •  | 
  •  

Domanda

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

Allora potrei fare:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Sfortunatamente, C# è generico in cui i vincoli non hanno restrizioni di enumerazione, 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?

È stato utile?

Soluzione

MODIFICARE:Questo è ora disponibile nella versione 0.0.0.2 di UnconstrainedMelody.

(Come richiesto sul mio post sul blog sui vincoli di enumerazione.Ho incluso i fatti di base di seguito per motivi di risposta autonoma.)

La soluzione migliore è aspettare che lo includa io Melodia non vincolata1.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 postbuild.

Non dovrebbe essere troppo difficile scriverlo IsSet...anche se catering per entrambi Int64-basato e UInt64-based flag potrebbero essere la parte difficile.(Sento che stanno arrivando alcuni metodi di supporto, che sostanzialmente mi permettono di trattare qualsiasi enumerazione di flag come se avesse un tipo base di UInt64.)

Quale vorresti che fosse il comportamento se chiamassi?

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

?Dovrebbe controllarlo Tutto sono impostati i flag specificati?Questa sarebbe la mia aspettativa.

Proverò a farlo stasera tornando a casa...Spero di avere una rapida occhiata ai metodi di enumerazione utili per portare rapidamente la libreria a uno standard utilizzabile, quindi rilassarmi un po'.

MODIFICARE:Non ne sono sicuro IsSet come nome, comunque.Opzioni:

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

Pensieri benvenuti.Sono sicuro che ci vorrà un po' prima che qualcosa venga scolpito nella pietra comunque...


1 o inviarlo come patch, ovviamente...

Altri suggerimenti

Darren, funzionerebbe se i tipi fossero enumerazioni specifiche: affinché le enumerazioni generali funzionino devi trasformarle in ints (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;
}

A partire da C# 7.3, ora esiste un modo integrato per aggiungere vincoli di enumerazione:

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

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

In realtà è possibile, con un brutto trucchetto.Tuttavia, non può essere utilizzato per 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 vuoi puoi donare Enums<Temp> un costruttore privato e una classe ereditata astratta annidata pubblica con Temp COME Enum, per impedire versioni ereditate per non enumerazioni.

Puoi ottenere questo risultato utilizzando IL Weaving e Vincoli extra

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

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

Il modo in cui lo faccio è inserire un vincolo struct, quindi verificare che T sia un'enumerazione in fase di esecuzione.Ciò non elimina completamente il problema, ma lo riduce leggermente

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

Utilizzando il codice originale, all'interno del metodo puoi anche utilizzare la riflessione 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;
    }
}

Ecco un codice che ho appena creato e che sembra funzionare come desideri 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.");
    }
}

se qualcuno ha bisogno di un IsSet generico (creato immediatamente al volo potrebbe essere migliorato) e/o della conversione da stringa a Enum al volo (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 caldo per creare un 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.

Volevo solo aggiungere Enum come vincolo generico.

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

Ho deciso di creare semplicemente un file struct vincolo e aggiungere un controllo di runtime per IsEnum.Per convertire una variabile da T a Enum, l'ho prima lanciata su 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());
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top