2012-05-24 15 views
7

In C Sharp, come posso impostare un'istruzione if che controlla se una delle diverse condizioni è vera? Deve essere solo una delle condizioni, se zero o due o più sono vere, se dovrebbe essere falso.Se istruzione - 'o' ma 'NON' e '

+0

@Tim S. Non è abbastanza corretto inserire la risposta nei commenti di diverse altre risposte. Cancellare quei commenti e postare la risposta come risposta e accettarne il giudizio dal voto - come intendeva Jeff. –

risposta

9

si potrebbe scrivere un metodo di supporto. Questo ha il vantaggio di cortocircuiti, solo valutare esattamente come molti come necessario,

public static bool IsExactlyOneTrue(IEnumerable<Func<bool>> conditions) { 
    bool any = false; 
    foreach (var condition in conditions) { 
     bool result = condition(); 
     if (any && result) { 
      return false; 
     } 
     any = any | result; 
    } 
    return any; 
} 
+0

Questo metodo può essere semplificato in 'return! Conditions.Where (cond => cond()). Skip (1) .Any();' È discutibilmente più leggibile e potrebbe rallentare i capelli, ma a meno che questo non sia effettivamente trovato per essere significativo, andrei per il più conciso. –

+1

Per ulteriori informazioni, questo deve essere più simile a questo per funzionare correttamente: 'var trueConditions = conditions.Where (cond => cond()); if (! TrueConditions.Take (1) .Any()) return false; return! TrueConditions.Skip (1) .Any(); 'Sto iniziando ad apprezzare la tua risposta meglio del mio approccio LINQ. –

+0

@TimS .: Ma ciò potrebbe causare la valutazione delle due espressioni leader risultanti da falsi. Non penso che ci sia un modo elegante di farlo in LINQ (eccetto che proiettando indici, che sconfigge l'intero scopo). – Douglas

4

Andando per semplicità, si può solo tenere un conteggio parziale:

int totalTrue = 0; 
if (A) totalTrue++; 
if (B) totalTrue++; 
if (C) totalTrue++; 
... 
return (1 == totalTrue); 
+0

Personalmente scriverei l'ultima istruzione come 'return totalTrue == 1;'.Capisco la logica per scrivere tali test a ritroso, ma non sono d'accordo con essa - e le parentesi non sono necessarie. –

+0

@KeithThompson, voglio solo mantenere l'abitudine di scrivere il codice nell'ordine in cui torno mai al C++. –

+0

@Adam V: Quando sei a Roma, fai come i romani. C# e C++ non sono la stessa lingua. Non scrivere C# in C++ e C++ in C#. – jason

5

Si potrebbe utilizzare comporre la vostra booleani in una sequenza bool e quindi applicare LINQ:

bool[] conditions = new bool[] { cond1, cond2, cond3, cond4 }; 
bool singleTrue = conditions.Count(cond => cond) == 1; 

solo per due booleani, esclusivo o diventa molto più semplice:

bool singleTrue = cond1 != cond2; 

Modifica: Per ottenere on-demand valutazione e corto circuito, è necessario promuovere la nostra sequenza bool in una sequenza Func<bool> (in cui ogni elemento è un delegato funzione incapsula la valutazione di una condizione):

IEnumerable<Func<bool>> conditions = // define sequence here 
int firstTrue = conditions.IndexOf(cond => cond()); 
bool singleTrue = firstTrue != -1 && 
        conditions.Skip(firstTrue + 1).All(cond => !cond()); 

quanto sopra frammento presuppone l'esistenza di un operatore IndexOf predicato-based, che non è disponibile sotto la versione corrente di LINQ ma può essere definita come un metodo di estensione in questo modo:

public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    int i = 0; 

    foreach (T element in source) 
    { 
     if (predicate(element)) 
      return i; 

     i++; 
    } 

    return -1; 
} 

campione d ata per il test (un punto di interruzione può essere impostato su ogni false o true a seguire di valutazione):

IEnumerable<Func<bool>> conditions = new Func<bool>[] 
{ 
    () => 
     false, 
    () => 
     true, 
    () => 
     false, 
    () => 
     false, 
}; 
+0

Questo ha la debolezza che è necessario valutare tutte le condizioni. – jason

+0

Ciò può essere corretto cambiandolo a 'Func []' e usando 'conditions.Where (cond => cond()). Skip (1) .Any();'. –

+0

@TimS .: Non sarebbe saltato 'Skip (1)' quando tutti i valori sono falsi? – Douglas

2

credo che questo sarebbe fare il trucco

int i= 0; 
if ((!A || ++i <= 1) && 
     (!B || ++i <= 1) && 
     (!C || ++i <= 1) && 
     ... && 
     (i == 1)) 

Se non ho pensato sbagliato su questo, questo if sarà falso non appena i > 1. Se i non è mai incrementato e raggiungiamo l'ultimo condion, sarà falso dal i == 0

5
List<Func<Customer, bool>> criteria = new List<Func<Customer, bool>>(); 

criteria.Add(c => c.Name.StartsWith("B")); 
criteria.Add(c => c.Job == Jobs.Plumber); 
criteria.Add(c => c.IsExcellent); 

Customer myCustomer = GetCustomer(); 

int criteriaCount = criteria 
    .Where(q => q(myCustomer)) 
    // .Take(2) // optimization 
    .Count() 
if (criteriaCount == 1) 
{ 
} 

implementazione Linq della firma del metodo di Jason:

public static bool IsExactlyOneTrue(IEnumerable<Func<bool>> conditions) 
{ 
    int passingConditions = conditions 
    .Where(x => x()) 
    // .Take(2) //optimization 
    .Count(); 
    return passingConditions == 1; 
} 
+0

Ottima risposta. Può anche farlo senza relazione con un tipo specifico: 'var conditions = new List >(); conditions.Add (() => foo == bar); if (conditions.Select (x => x.Invoke()). Count (x => x) == 1) {} ' – lukiffer

+0

Certo, stai solo spingendo le istanze specifiche nei criteri. La mia raccolta "criteri" ritarda la necessità di avere un'istanza applicabile ed è riutilizzabile per tutte le istanze quante ne hai. –

+0

Questo approccio richiede la valutazione di tutte le condizioni, indipendentemente dal fatto che sia necessario. Preferisco un approccio che consenta cortocircuiti. – jason

0

La maggior parte di queste risposte saranno lavorare e avere ' buona performance'. Ma la risposta più semplice è:

if((A & !(B || C)) || 
    (B & !(A || C)) || 
    (C & !(A || B))) 
{ 
    ... 
} 

si finisce per valutare A/B/C più di una volta quindi questo è veramente utile solo quando si dispone bools semplici.

Problemi correlati