Activar Enum (con el atributo Flags) sin declarar todas las combinaciones posibles?


¿Cómo enciendo una enumeración que tiene el atributo flags establecido (o más precisamente se usa para operaciones de bits) ?

Quiero ser capaz de golpear todos los casos en un interruptor que coincide con los valores declarados.

El problema es que si tengo la siguiente enumeración

[Flags()]public enum CheckType
{
    Form = 1,   
    QueryString = 2,
    TempData = 4,
}

Y quiero usar un interruptor como este

switch(theCheckType)
{
   case CheckType.Form:
       DoSomething(/*Some type of collection is passed */);
       break;

   case CheckType.QueryString:
       DoSomethingElse(/*Some other type of collection is passed */);
       break;

   case CheckType.TempData
       DoWhatever(/*Some different type of collection is passed */);
       break;
}

Si "theCheckType" se establece en ambos CheckType.Formulario | CheckType.TempData quiero que golpee ambos casos. Obviamente no golpeará ambos en mi ejemplo debido a el break, pero aparte de eso también falla porque CheckType.La forma no es igual a CheckType.Formulario | CheckType.TempData

La única solución entonces como puedo ver es hacer un caso para cada posible combinación de los valores de enumeración ?

Algo como

    case CheckType.Form | CheckType.TempData:
        DoSomething(/*Some type of collection is passed */);
        DoWhatever(/*Some different type of collection is passed */);
        break;

    case CheckType.Form | CheckType.TempData | CheckType.QueryString:
        DoSomething(/*Some type of collection is passed */);
        DoSomethingElse(/*Some other type of collection is passed */);
        break;

... and so on...

Pero eso realmente no es muy deseado (ya que rápidamente crecerá muy grande)

Ahora mismo tengo 3 Si las condiciones justo después de uno mismo en lugar

Algo como

if ((_CheckType & CheckType.Form) != 0)
{
    DoSomething(/*Some type of collection is passed */);
}

if ((_CheckType & CheckType.TempData) != 0)
{
    DoWhatever(/*Some type of collection is passed */);
}

....

Pero eso también significa que si tengo una enumeración con 20 valores, tiene que pasar por 20 condiciones If cada vez en lugar de "saltar" a solo los "casos"necesarios como cuando se usa un interruptor.

¿hay alguna solución mágica para resolver este problema?

He pensado en la posibilidad de recorrer los valores declarados y luego usar el interruptor, entonces solo golpearía el interruptor para cada valor declarado, pero no se cómo funcionará y si el vicio de rendimiento es una buena idea (en comparación con muchos if) ?

¿Hay una manera fácil de recorrer todos los valores de enumeración declarados ?

Solo puedo llegar a usar toString() y dividir por "," y luego recorrer el array y analizar cada cadena.


ACTUALIZACIÓN:

Veo que no he hecho un buen trabajo explicando. Mi ejemplo es a simple (trató de simplificar mi escenario).

Lo uso para un atributo ActionMethodSelectorAttribute en Asp.net MVC para determinar si un método debe estar disponible al resolver la url / ruta.

Lo hago declarando algo como esto en el método

[ActionSelectorKeyCondition(CheckType.Form | CheckType.TempData, "SomeKey")]
public ActionResult Index()
{
    return View();
} 

Eso significaría que debería verificar si el Formulario o TempData tienen una clave como se especifica para que el método esté disponible.

Los métodos a los que llamará (doSomething (), doSomethingElse () y doWhatever () en mi ejemplo anterior) tendrán bool como valor de retorno y se llamarán con un parámetro (diferentes colecciones que no comparten una interfaz que se pueda usar-ver mi ejemplo código en el siguiente enlace, etc.).

Para dar una mejor idea de lo que estoy haciendo, he pegado un ejemplo simple de lo que realmente estoy haciendo en pastebin-se puede encontrar aquí http://pastebin.com/m478cc2b8

Author: KeyWeeUsr, 2009-06-25

8 answers

Qué tal esto. Por supuesto, los argumentos y los tipos de retorno de doSomething, etc., puede ser lo que quieras.

class Program
{
    [Flags]
    public enum CheckType
    {
        Form = 1,
        QueryString = 2,
        TempData = 4,
    }

    private static bool DoSomething(IEnumerable cln)
    {
        Console.WriteLine("DoSomething");
        return true;
    }

    private static bool DoSomethingElse(IEnumerable cln)
    {
        Console.WriteLine("DoSomethingElse");
        return true;
    }

    private static bool DoWhatever(IEnumerable cln)
    {
        Console.WriteLine("DoWhatever");
        return true;
    }

    static void Main(string[] args)
    {
        var theCheckType = CheckType.QueryString | CheckType.TempData;
        var checkTypeValues = Enum.GetValues(typeof(CheckType));
        foreach (CheckType value in checkTypeValues)
        {
            if ((theCheckType & value) == value)
            {
                switch (value)
                {
                    case CheckType.Form:
                        DoSomething(null);
                        break;
                    case CheckType.QueryString:
                        DoSomethingElse(null);
                        break;
                    case CheckType.TempData:
                        DoWhatever(null);
                        break;
                }
            }
        }
    }
}
 41
Author: Jamie Ide,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-06 23:51:45

Las enumeraciones de Flags se pueden tratar como un tipo integral simple en el que cada bit individual corresponde a uno de los valores marcados. Puede explotar esta propiedad para convertir el valor de enumeración marcado por bits en una matriz de booleanos y, a continuación, enviar los métodos que le interesan desde una matriz correlacionada de delegados.

EDITAR: Ciertamente podríamos hacer este código más compacto a través del uso de LINQ y algunas funciones auxiliares, pero creo que es más fácil de entender en el menor forma sofisticada. Este puede ser el caso donde el mantenimiento triunfa sobre la elegancia.

Aquí hay un ejemplo:

[Flags()]public enum CheckType
{
  Form = 1,       
  QueryString = 2,
  TempData = 4,
}

void PerformActions( CheckType c )
{
  // array of bits set in the parameter {c}
  bool[] actionMask = { false, false, false };
  // array of delegates to the corresponding actions we can invoke...
  Action availableActions = { DoSomething, DoSomethingElse, DoAnotherThing };

  // disassemble the flags into a array of booleans
  for( int i = 0; i < actionMask.Length; i++ )
    actionMask[i] = (c & (1 << i)) != 0;

  // for each set flag, dispatch the corresponding action method
  for( int actionIndex = 0; actionIndex < actionMask.Length; actionIndex++ )
  {
      if( actionMask[actionIndex])
          availableActions[actionIndex](); // invoke the corresponding action
  }
}

Alternativamente, si el orden en el que evalúas no importa, aquí hay una solución más simple y clara que funciona igual de bien. Si el orden importa, reemplace las operaciones de desplazamiento de bits con un array que contenga los indicadores en el orden en el que desea evaluarlos:

int flagMask = 1 << 31; // start with high-order bit...
while( flagMask != 0 )   // loop terminates once all flags have been compared
{
  // switch on only a single bit...
  switch( theCheckType & flagMask )
  {
   case CheckType.Form:
     DoSomething(/*Some type of collection is passed */);
     break;

   case CheckType.QueryString:
     DoSomethingElse(/*Some other type of collection is passed */);
     break;

   case CheckType.TempData
     DoWhatever(/*Some different type of collection is passed */);
     break;
  }

  flagMask >>= 1;  // bit-shift the flag value one bit to the right
}
 13
Author: LBushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-01 18:19:53

¿Qué hay de un Dictionary<CheckType,Action> que llenarás como

dict.Add(CheckType.Form, DoSomething);
dict.Add(CheckType.TempDate, DoSomethingElse);
...

Una descomposición de su valor

flags = Enum.GetValues(typeof(CheckType)).Where(e => (value & (CheckType)e) == (CheckType)e).Cast<CheckType>();

Y luego

foreach (var flag in flags)
{
   if (dict.ContainsKey(flag)) dict[flag]();
}

(código no probado)

 4
Author: Rauhotz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-24 20:45:17

Solo use HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);
 3
Author: Stijn Van Antwerpen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-12 10:24:52

Con C # 7 ahora puedes escribir algo como esto:

public void Run(CheckType checkType)
{
    switch (checkType)
    {
        case var type when CheckType.Form == (type & CheckType.Form):
            DoSomething(/*Some type of collection is passed */);
            break;

        case var type when CheckType.QueryString == (type & CheckType.QueryString):
            DoSomethingElse(/*Some other type of collection is passed */);
            break;

        case var type when CheckType.TempData == (type & CheckType.TempData):
            DoWhatever(/*Some different type of collection is passed */);
            break;
    }
}
 2
Author: Alex Sanséau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-06 11:49:46

Basado en su edición y su código de la vida real, probablemente actualizaría el método IsValidForRequest para que se vea algo como esto:

public sealed override bool IsValidForRequest
    (ControllerContext cc, MethodInfo mi)
{
    _ControllerContext = cc;

    var map = new Dictionary<CheckType, Func<bool>>
        {
            { CheckType.Form, () => CheckForm(cc.HttpContext.Request.Form) },
            { CheckType.Parameter,
                () => CheckParameter(cc.HttpContext.Request.Params) },
            { CheckType.TempData, () => CheckTempData(cc.Controller.TempData) },
            { CheckType.RouteData, () => CheckRouteData(cc.RouteData.Values) }
        };

    foreach (var item in map)
    {
        if ((item.Key & _CheckType) == item.Key)
        {
            if (item.Value())
            {
                return true;
            }
        }
    }
    return false;
}
 1
Author: LukeH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-24 23:24:44

Debería ser posible en C # 7

switch (t1)
    {
        case var t when t.HasFlag(TST.M1):
            {
                break;
            }
        case var t when t.HasFlag(TST.M2):
            {
                break;
            }
 0
Author: justromagod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-12 08:04:26

La forma más fácil es simplemente realizar una enumeración ORed, en su caso podría hacer lo siguiente:

[Flags()]public enum CheckType
{
    Form = 1,   
    QueryString = 2,
    TempData = 4,
    FormQueryString = Form | QueryString,
    QueryStringTempData = QueryString | TempData,
    All = FormQueryString | TempData
}

Una vez que tenga la configuración enum, ahora es fácil realizar su instrucción switch.

Por ejemplo, si he establecido lo siguiente:

var chkType = CheckType.Form | CheckType.QueryString;

Puedo usar la siguiente declaración switch de la siguiente manera:

switch(chkType){
 case CheckType.Form:
   // Have Form
 break;
 case CheckType.QueryString:
   // Have QueryString
 break;
 case CheckType.TempData:
  // Have TempData
 break;
 case CheckType.FormQueryString:
  // Have both Form and QueryString
 break;
 case CheckType.QueryStringTempData:
  // Have both QueryString and TempData
 break;
 case CheckType.All:
  // All bit options are set
 break;
}

Mucho más limpio y no necesita usar una instrucción if con HasFlag. Puede hacer cualquier combinación que desee y luego hacer que la declaración switch sea fácil de Leer.

Yo recomendaría romper su enums, trate de ver si usted no está mezclando diferentes cosas en el mismo enum. Puede configurar múltiples enums para reducir el número de casos.

 -1
Author: Joseph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-04 20:30:04