2009-06-24 18 views
39

come posso attivare un enum che ha l'attributo flags impostato (o più precisamente viene usato per le operazioni bit)?Accendi Enum (con attributo Flags) senza dichiarare ogni combinazione possibile?

Voglio essere in grado di colpire tutti i casi in un interruttore che corrisponde ai valori dichiarati.

Il problema è che se ho il seguente enum

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

e voglio utilizzare un interruttore come questo

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

Se "theCheckType" è impostato su entrambi CheckType.Form | CheckType.TempData Voglio che colpisca entrambi i casi. Ovviamente non verrà colpito nel mio esempio a causa dell'interruzione, ma a parte questo fallisce anche perché CheckType.Form non è uguale a CheckType.Form | CheckType.TempData

L'unica soluzione quindi, come posso vedere, è creare un caso per ogni possibile combinazione dei valori enumerati?

Qualcosa di simile

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... 

ma che in realtà è neanche molto desiderata (come sarà rapidamente crescerà molto grande)

In questo momento ho 3 Se le condizioni di destra dopo la vicenda, invece

Qualcosa di simile

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

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

.... 

Ma questo significa anche che se ho un enum wi i 20 valori devono passare attraverso 20 Se le condizioni ogni volta invece di "saltare" solo al "caso"/s necessario come quando si usa un interruttore.

C'è qualche soluzione magica per risolvere questo problema?

Ho pensato alla possibilità di eseguire il loop dei valori dichiarati e quindi utilizzare lo switch, quindi avrebbe colpito l'interruttore solo per ogni valore dichiarato, ma non so come funzionerà e se il rendimento è vice una buona idea (rispetto a molte di se)?

Esiste un modo semplice per eseguire il ciclo di tutti i valori enumerati dichiarati?

Posso solo utilizzare ToString() e dividere per "," e quindi scorrere l'array e analizzare ogni singola stringa.


UPDATE:

vedo che non ho fatto un lavoro abbastanza buono per spiegare. Il mio esempio è semplice (ho cercato di semplificare il mio scenario).

Lo uso per un ActionMethodSelectorAttribute in Asp.net MVC per determinare se un metodo deve essere disponibile quando si risolve l'url/route.

lo faccio dichiarando qualcosa di simile sul metodo

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

Ciò significherebbe che dovrebbe controllare se il modulo o TempData hanno una chiave, come specificato per il metodo sia disponibile.

I metodi che chiamerà (doSomething(), doSomethingElse() e doWhatever() nell'esempio precedente) avranno in realtà bool come valore di ritorno e verranno chiamati con un parametro (raccolte diverse che non condividono un interfaccia che può essere utilizzata - vedi il mio codice di esempio nel link sottostante ecc.).

Per spera dare una migliore idea di quello che sto facendo ho incollato un semplice esempio di quello che sto facendo su pastebin - può essere trovato qui http://pastebin.com/m478cc2b8

risposta

39

Che ne dici di questo. Ovviamente gli argomenti e i tipi di reso di DoSomething, ecc. Possono essere qualsiasi cosa tu voglia.

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

Grazie per la risposta. È praticamente la soluzione di Lukes che utilizza lo switch al posto della mappatura del dizionario. Questa sembra essere la soluzione migliore anche se ho davvero sperato che fosse possibile in qualche modo solo il ciclo dei valori in "theCheckType" invece di fare il ciclo dell'intera enumerazione, poiché non c'è davvero alcun motivo per controllare tutti i valori enum, solo il quelli impostati in "theCheckType". Ma immagino che non sia possibile. – MartinF

+0

Non vedo la somiglianza con la risposta di Luca; Penso che il mio sia significativamente più semplice.Per come la vedo io, devi controllare "theCheckType" contro l'enum o controllare l'enum contro "theCheckType". In entrambi i casi è necessario verificare tutti i valori possibili. Se dichiari il tuo enum a lungo (Int64) puoi ovviamente avere un massimo di 64 valori, quindi non c'è bisogno di preoccuparsi delle prestazioni in un ciclo così breve. –

+0

Dai un'occhiata a questo: http://stackoverflow.com/questions/1060760/what-to-do-when-mas-mask-flags-enum-gets-too-large –

4

Che dire di un Dictionary<CheckType,Action> che si riempirà come

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

una scomposizione del valore

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

e poi

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

(codice non testato)

+0

Grazie per la risposta. Ho aggiornato il mio post originale. Non penso che sia possibile usare un dizionario come lo sto dichiarando in un attributo (vedi esempio) - o almeno non so come dovrebbe essere fatto. – MartinF

+0

Forse non adeguato per il problema di MartinF, ma molto elegante ... +1;) –

+0

Enum (...). GetValues ​​restituisce una matrice; non puoi usare 'Dove. – BillW

13

Flags enums può essere trattata come un semplice tipo integrale in cui ogni singolo bit corrisponde ad uno dei valori segnalati. È possibile sfruttare questa proprietà per convertire il valore enum bit-flag in una matrice di valori booleani e quindi inviare i metodi che si interessano a una matrice correlata di delegati.

EDIT:Ci potrebbe certamente rendere questo codice più compatto attraverso l'utilizzo di LINQ e alcune funzioni di aiuto, ma penso che sia più facile da capire in forma meno sofisticati. Questo potrebbe essere il caso in cui la manutenibilità vince sull'eleganza.

Ecco un esempio:

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

In alternativa, se l'ordine in cui si valuta non importa, qui è più semplice soluzione, più chiaro che funziona altrettanto bene. Se l'ordine non importa, sostituire le operazioni bit-shifting, con una matrice contenente le bandiere nell'ordine in cui si desidera valutare in:

int flagValue = 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 
} 
+0

Soluzione molto bella! – Henri

+0

Grazie per la risposta. Ho aggiornato la mia domanda originale. Sembra una buona soluzione, ma sfortunatamente il modo in cui il mio codice è strutturato, ho bisogno di ottenere un valore di ritorno dal metodo che viene chiamato e anche di passare diversi tipi di parametri (ho collegato ad un esempio di quello che sto cercando di fare al parte inferiore). Spero che il codice di esempio spieghi meglio il mio problema rispetto al mio testo. :) – MartinF

1

base alla tua modifica e il codice di vita reale, probabilmente sarei aggiorno il metodo IsValidForRequest è simile al seguente:

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

Grazie per la risposta. Non vedo come sarebbe diverso dalle condizioni "Se". Questo passerebbe attraverso ogni valore in "map" (e ogni valore nell'enumer CheckType dovrebbe essere registrato nel dizionario "map"). Se vuoi dire che dovrei dichiarare il dizionario nell'attributo, non penso che sia possibile. Ho aggiornato la mia domanda originale con un collegamento ad un codice di esempio di ciò che sto effettivamente cercando di fare, per migliorarmi, si spera, meglio. – MartinF

+0

@MartinF, basato sul codice di esempio aggiornato! Hai ragione che non è molto diverso dalle tue "se" condizioni. L'unico vantaggio è che qualsiasi nuovo valore CheckType deve essere incluso nella mappa. – LukeH

+0

Non sono ancora del tutto chiaro perché pensi che le tue affermazioni "se" siano un problema. Se stai cercando di evitarli per motivi di prestazioni, questo suona molto simile a un'ottimizzazione prematura per me. – LukeH

1

Basta usare HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...); 
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...); 
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...); 
0

Il modo più semplice è quello di eseguire solo un ORed enum, nel tuo caso si potrebbe procedere come segue:

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

Una volta che hai il setup enum è ora facile eseguire la tua istruzione switch.

Ad esempio, se ho impostato il seguente:

var chkType = CheckType.Form | CheckType.QueryString; 

posso usare il switch dichiarazione seguente come segue:

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

molto più pulito e che non è necessario utilizzare un if dichiarazione con HasFlag. Puoi fare tutte le combinazioni che vuoi e quindi rendere l'istruzione switch facile da leggere.

Mi raccomando di rompere il tuo enums, prova a vedere se non stai mescolando cose diverse nello stesso enum. È possibile impostare più enums per ridurre il numero di casi.