¿Alguien conoce una buena solución para la falta de una restricción genérica de enumeración?

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

  •  08-06-2019
  •  | 
  •  

Pregunta

Lo que quiero hacer es algo como esto:Tengo enumeraciones con valores marcados combinados.

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

Entonces podría hacer:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Desafortunadamente, las restricciones genéricas de C# no tienen restricción de enumeración, solo clase y estructura.C# no ve enumeraciones como estructuras (aunque sean tipos de valor), por lo que no puedo agregar tipos de extensión como este.

¿Alguien sabe una solución?

¿Fue útil?

Solución

EDITAR:Esto ya está disponible en la versión 0.0.0.2 de UnconstrainedMelody.

(Según lo solicitado en mi publicación de blog sobre restricciones de enumeración.He incluido los datos básicos a continuación para obtener una respuesta independiente).

La mejor solución es esperar a que lo incluya en Melodía sin restricciones1.Esta es una biblioteca que toma código C# con restricciones "falsas" como

where T : struct, IEnumConstraint

y lo convierte en

where T : struct, System.Enum

a través de un paso posterior a la compilación.

No debería ser demasiado difícil escribir IsSet...aunque atiende a ambos Int64-basado y UInt64Las banderas basadas en -podrían ser la parte complicada.(Huelo que se avecinan algunos métodos de ayuda, que básicamente me permiten tratar cualquier enumeración de banderas como si tuviera un tipo base de UInt64.)

¿Cuál le gustaría que fuera el comportamiento si llamara?

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

?¿Debería comprobar que todo ¿Están configuradas las banderas especificadas?Esa sería mi expectativa.

Intentaré hacer esto de camino a casa esta noche...Espero tener un rápido bombardeo de métodos de enumeración útiles para que la biblioteca alcance un estándar utilizable rápidamente y luego relajarme un poco.

EDITAR:no estoy seguro de IsSet como nombre, por cierto.Opciones:

  • Incluye
  • Contiene
  • HasFlag (o HasFlags)
  • IsSet (ciertamente es una opción)

Pensamientos bienvenidos.Estoy seguro de que pasará un tiempo antes de que algo esté escrito en piedra de todos modos...


1 o enviarlo como parche, por supuesto...

Otros consejos

Darren, eso funcionaría si los tipos fueran enumeraciones específicas; para que funcionen las enumeraciones generales, debes convertirlas en ints (o más probablemente en uint) para hacer los cálculos booleanos:

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

A partir de C# 7.3, ahora hay una forma integrada de agregar restricciones de enumeración:

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

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

En realidad, es posible, con un feo truco.Sin embargo, no se puede utilizar para métodos de extensión.

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 quieres puedes dar Enums<Temp> un constructor privado y una clase heredada abstracta anidada pública con Temp como Enum, para evitar versiones heredadas para no enumeraciones.

Puedes lograr esto usando IL Weaving y restricciones adicionales

Le permite escribir este código.

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

Lo que se compila

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

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

Esto no responde a la pregunta original, pero ahora hay un método en .NET 4 llamado Enum.HasFlag que hace lo que estás tratando de hacer en tu ejemplo

La forma en que lo hago es poner una restricción de estructura y luego verificar que T sea una enumeración en tiempo de ejecución.Esto no elimina el problema por completo, pero sí lo reduce un poco.

A partir de C# 7.3, puedes usar la restricción Enum en tipos genéricos:

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

Si desea utilizar una enumeración anulable, debe dejar la restricción de estructura original:

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

Usando su código original, dentro del método también puede usar la reflexión para probar que T es una enumeración:

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

Aquí hay un código que acabo de crear y que parece funcionar como quieres sin tener que hacer nada demasiado loco.No se limita solo a enumeraciones configuradas como Banderas, pero siempre se puede realizar una verificación si es necesario.

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 alguien necesita un IsSet genérico (creado fuera de la caja sobre la marcha podría mejorarse) o una cadena para la conversión de Enum onfly (que utiliza EnumConstraint que se presenta a continuación):

  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 alguien todavía necesita un ejemplo activo para crear una restricción de codificación 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));
  }

}

Espero que esto ayude a alguien.

Sólo quería agregar Enum como restricción genérica.

Si bien esto es sólo para un pequeño método de ayuda usando ExtraConstraints Es demasiado para mí.

Decidí simplemente crear un struct restricción y agregar una verificación de tiempo de ejecución para IsEnum.Para convertir una variable de T a Enum, primero la convierto en objeto.

    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());
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top