2008-08-10 10 views
79

Lo que quiero hacer es algo como esto: Tengo enumeraciones con valores marcados combinados.¿Alguien sabe una buena solución para la falta de una restricción genérica enum?

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 yo podría hacer:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB 

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

Desafortunadamente C# 's genérico donde las restricciones tienen ninguna restricción de enumeración, única clase y estructura. C# no ve las enumeraciones como estructuras (aunque sean tipos de valores), así que no puedo agregar tipos de extensión como este.

¿Alguien sabe una solución?

+2

Keith: descargue la versión 0.0.0.2 de UnconstrainedMelody - He implementado HasAll y HasAny. Disfrutar. –

+0

¿Qué quiere decir con "C# no ve las enumeraciones como estructuras"? Puede usar tipos de enumeración como parámetros de tipo que están limitados a 'struct' muy bien. – Timwi

+0

revise este artículo aquí: http://www.codeproject.com/KB/cs/ExtendEnum.aspx 'IsValidEnumValue' o 'IsFlagsEnumDefined' son probablemente la respuesta a su pregunta. – dmihailescu

Respuesta

46

EDITAR: Esto ahora es en vivo en la versión 0.0.0.2 de UnconstrainedMelody.

(Conforme a lo solicitado en mi blog post about enum constraints. He incluido los datos básicos siguientes en aras de una respuesta independiente.)

La mejor solución es esperar a que me incluyo en UnconstrainedMelody . Esta es una biblioteca que lleva el código C# con limitaciones "falsos" como

where T : struct, IEnumConstraint 

y lo convierte en

where T : struct, System.Enum 

a través de un paso postbuild.

No debería ser demasiado difícil escribir IsSet ... aunque el suministro de indicadores basados ​​en Int64 y UInt64 podría ser la parte difícil. (Huelo algunos métodos de ayuda que se acerca, básicamente, lo que me permite tratar cualquier banderas enumeración como si tuviera un tipo base de UInt64.)

¿Qué desea que el comportamiento sea si se llama a

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

? ¿Debe verificar que todos estén establecidos los indicadores especificados? Esa sería mi expectativa.

Trataré de hacer esto en el camino a casa esta noche ... Espero tener un ataque relámpago rápido sobre métodos de enumeración útiles para que la biblioteca alcance un nivel utilizable rápidamente, y luego relajarme un poco.

EDITAR: No estoy seguro acerca de IsSet como un nombre, por cierto.Opciones:

  • Incluye
  • Contiene
  • HasFlag (o HasFlags)
  • isset (que es sin duda una opción)

Pensamientos de bienvenida. Estoy seguro de que pasará un tiempo antes de la puesta de nada en piedra de todos modos ...


o enviarla como un parche, por supuesto ...

+0

Supongo que si se pasan varias banderas en ella debería verificarlas todas. Mi solución actual para esto (allá por 2008 cuando lo pregunté) era tener un método de extensión de plantilla para cada enum de banderas: desordenado, pero funciona. Nunca me molesté con el control de múltiples banderas porque todos los controles que tenemos son para una sola bandera, no es un problema en el código interno único, sino algo que debería tenerse en cuenta en una biblioteca compartida. – Keith

+1

Debes ir y mencionar PostSharp LOL: o http://www.postsharp.org/blog/generic-constraints-for-enums-and-delegates –

+0

Usaría la terminología de Flags simplemente porque ya existe en .NET (Ver 'FlagsAttribute'.) Veo dos nombres explícitos aquí:' HasAnyFlag' y 'HasAllFlags'. Se pueden acortar a 'HasFlag' y' HasFlags'. No puedo decir cuál es la mejor, es una cuestión de gusto, supongo. – Blixt

16

Darren, que funcionaría si los tipos eran enumeraciones específicas - para enumeraciones generales para trabajar hay que echarlos a enteros (o más probablemente uint) para hacer los cálculos booleano:

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

Y si tiene un número ridículo de indicadores, puede llamar a GetTypeCode() en los argumentos y Convert.ToUint64() – Kit

+0

Impresionante, la combinación de 'Enum' y 'Convert.ToUInt32' que no encontré en ningún otro lado. AFAIK, es la única solución decente Pre-Net-4 que también funciona en VB. Por cierto, si 'matchTo' podría tener múltiples bits de indicador, entonces reemplace'! = 0' con '== Convert.ToUInt32 (matchTo)'. – ToolmakerSteve

+1

Tenga en cuenta que 'Convert.ToUInt32' utilizado con una enumeración usará la sobrecarga' Convert.ToUInt32 (object) ', lo que significa que CLR primero colocará estos valores antes de pasarlos al método' ToUInt32'. En la mayoría de los casos, esto no importará, pero es bueno saber que mantendrá ocupado el GC si está usando algo como esto para analizar millones de enumeraciones por segundo. – Groo

1

Usando el original código, dentro del método también se puede utilizar 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; 
    } 
} 
+2

Gracias, pero eso convierte un problema de tiempo de compilación (la restricción where) en un tiempo de ejecución (su excepción). También necesitaría convertir las entradas en entradas antes de poder hacer nada con ellas. – Keith

3

la manera de hacerlo es poner una restricción de estructura, a continuación, comprobar que T es una enumeración en tiempo de ejecución. Esto no elimina el problema por completo, pero lo reduce un poco

+7

donde T: struct, IComparable, IFormattable, IConvertible - esto es lo más cercano que se puede obtener enum :) – Kit

9

En realidad, es posible con un feo truco Sin embargo, no se puede usar 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 desea, puede dar Enums<Temp> un constructor privado y una clase abstracta anidada pública heredada con Temp como Enum, para evitar que las versiones heredadas de los no enumeraciones.

1

Aquí hay un código que acabo de hacer que parece funcionar como usted quiere sin tener que hacer nada demasiado loco. No está restringido solo a las enumeraciones configuradas como Indicadores, pero siempre puede haber un control puesto 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."); 
    } 
} 
4

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

+0

Se modificó la recomendación porque, en este momento, la mayoría de la gente debería estar usando .NET 4 (o superior) y deberían usar este método en vez de intentar hacerlo juntos. – CptRobby

+0

Upvoted. Sin embargo, su solución usa el boxeo del argumento 'flag'. .NET 4.0 tiene cinco años ahora. –

8

Esto se puede conseguir usando Weaving IL y ExtraConstraints

le permite escribir el código

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

Lo que se compiló

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

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

Yo sólo quería añadir Enum como una limitación genérica.

Si bien esto es solo para un pequeño método de ayuda utilizando ExtraConstraints es demasiado para mí.

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

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()); 
    } 
0

si alguien necesita genérica isset (creado de la caja en marcha podría mejorarse sucesivamente), y o cuerda para Enum conversión onfly (que utiliza EnumConstraint presentan 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 ejemplo caliente para crear la 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.

Cuestiones relacionadas