5

Prima di iniziare a criticare e avermi §8.7.2 di C# specification, leggere attentamente :)istruzione Switch con i non-espressione-costante - Estende la capacità C#/IDE

Sappiamo tutti quanto l'interruttore si presenta come in C#. Ok, quindi prendere in considerazione la classe MainWindow con "nasty" Bar metodo

static int barCounter = 0; 
public static int Bar() 
{ 
    return ++barCounter; 
} 

Da qualche parte in questa classe abbiamo codice come questo

Action switchCode =() => 
{ 
    switch (Bar()) 
    { 
     case 1: 
      Console.WriteLine("First"); 
      break; 
     case 2: 
      Console.WriteLine("Second"); 
      break; 
    } 
}; 

switchCode(); 

switchCode(); 

Nella finestra della console vedremo

First 
Second 

Usando le espressioni in C# potremmo fare lo stesso - scrivere lo stesso codice

var switchValue = Expression.Call(typeof(MainWindow).GetMethod("Bar")); 

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }); 

var @switch = Expression.Switch(switchValue, 
    Expression.SwitchCase(
     Expression.Call(WriteLine, Expression.Constant("First")), 
     Expression.Constant(1) 
     ), 
    Expression.SwitchCase(
     Expression.Call(WriteLine, Expression.Constant("Second")), 
     Expression.Constant(2) 
     ) 
    ); 

Action switchCode = Expression.Lambda<Action>(@switch).Compile(); 

switchCode(); 

switchCode(); 

In DebugView abbiamo potuto vedere "code-behind" questa espressione

.Switch (.Call WpfApplication1.MainWindow.Bar()) { 
.Case (1): 
     .Call System.Console.WriteLine("First") 
.Case (2): 
     .Call System.Console.WriteLine("Second") 
} 

EHMM, cosa succede se usiamo Expression.Call invece Expression.Constant?

public static bool foo1() { return false; } 

public static bool foo2() { return true; } 

// ..... 

var foo1 = Ex.Call(typeof(MainWindow).GetMethod("foo1")); 
var foo2 = Ex.Call(typeof(MainWindow).GetMethod("foo2")); 
var switchValue = Ex.Call(typeof(MainWindow).GetMethod("Bar")); 

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }); 

var @switch = Ex.Switch(Ex.Constant(true), 
    Ex.SwitchCase(
     Ex.Call(WriteLine, Ex.Constant("First")), 
     foo1 
     ), 
    Ex.SwitchCase(
     Ex.Call(WriteLine, Ex.Constant("OK!")), 
     Ex.Equal(switchValue, Ex.Constant(2)) 
     ), 
    Ex.SwitchCase(
     Ex.Call(WriteLine, Ex.Constant("Second")), 
     foo2 
     ) 
    ); 

Action switchCode = Ex.Lambda<Action>(@switch).Compile(); 

switchCode(); 

switchCode(); 

spettacoli finestra della console, come ci aspettavamo

Second 
OK! 

E DebugView

.Switch (True) { 
.Case (.Call WpfApplication1.MainWindow.foo1()): 
     .Call System.Console.WriteLine("First") 
.Case (.Call WpfApplication1.MainWindow.Bar() == 2): 
     .Call System.Console.WriteLine("OK!") 
.Case (.Call WpfApplication1.MainWindow.foo2()): 
     .Call System.Console.WriteLine("Second") 
} 

Quindi è possibile utilizzare l'espressione non costante nel caso-statement :)

Ok, ho capito che questo è un piccolo codice "disordinato". Ma ecco che arriva la mia domanda (finalmente: P):
Esiste un modo per estendere la funzionalità di IDE/VisualStudio/compilatore per farlo, ma con un codice più elegante?
Qualcosa di simile

switch (true) 
{ 
    case foo1(): 
     Console.WriteLine("First"); 
     break; 
    case Bar() == 2: 
     Console.WriteLine("OK!"); 
     break; 
    case foo2(): 
     Console.WriteLine("Second"); 
     break; 
} 

So che questo sarà un po 'di estensione e il codice non sarà lo stesso (non la stessa prestazione). Ma mi chiedo se sia addirittura possibile "cambiare" il codice al volo - come la funzione anonima o il rendimento restituito è trasformato in classe annidata.

Spero che qualcuno riesca a leggere il testo sopra e lasciare qualche indizio.

+4

Sembra un sacco di sforzi per risolvere un problema che non esiste. Gli switch possono offrire un tempo di ricerca costante. Questa è una grande cosa in scenari in cui hai un gran numero di valori possibili. Basta usare una serie di 'if/else if' e passare alla risoluzione di problemi reali (o usare un linguaggio che favorisca l'espressività oltre le prestazioni su tutta la linea). –

+0

@ Eds. Pensi di non sapere di if/else if? Questo è più "una sfida" su come fare questo, non un problema "come posso usare una serie di dichiarazioni di condizione booleana". In effetti ho usato PHP e JavaScript, c'è qualcosa come sopra molto. Se vuoi qualche problema reale: http://stackoverflow.com/q/9536087/1245315 – Carnifex

+1

No, penso che stai sprecando il tuo tempo e non utilizzandolo per risolvere problemi reali. Sì, ci sono linguaggi che possono fare ciò che tu proponi, ma non possono nemmeno offrire una ricerca a tempo costante. Hai solo una quantità limitata di tempo in questa vita, il mio consiglio è di non sprecarlo in cose come questa che non hanno alcun beneficio apprezzabile per nessuno. Un linguaggio di programmazione è solo uno strumento, non un obiettivo a sé stante. Per quanto riguarda la tua altra domanda ... no grazie, le stranezze dell'interfaccia utente non mi interessano, ed è per questo che non costruisco l'interfaccia utente per vivere :) –

risposta

1

No, non è possibile, ne sono a conoscenza. È già una specie di miracolo che è possibile utilizzare un string all'interno dell'istruzione switch (tipo di riferimento con comportamento immutabile). Per questo tipo di casi è sufficiente utilizzare le combinazioni if, if/else, if/elseif.

+2

Mentre sono completamente d'accordo con la tua risposta, non ho idea del perché sia ​​un miracolo usare le stringhe all'interno dello switch, perché quello che stai effettivamente usando è una costante in fase di compilazione (valore stringa letterale). –

+0

Ad esempio in 'C++' non è possibile usare una stringa nell'istruzione 'switch'. La nozione di 'type',' clear comparison ability' è qualcosa che viene fornito in 'C#' "gratuitamente". Cioè, il mio punto era solo notare la bontà che ".Net" ci ha portato. – Tigran

1

Attualmente non ci sono estensioni che fanno questo genere di cose. Anche se vale la pena di sottolineare che MS SQL permette esattamente quello che stai cercando

SELECT 
    Column1, Column2, 
    CASE 
    WHEN SomeCondition THEN Column3 
    WHEN SomeOtherCondition THEN Column4 
    END AS CustomColumn 
FROM ... 

Il problema con questa diventa la precedenza comprensione; cosa succede quando entrambe le condizioni sono vere?In SQL l'istruzione case restituisce il valore dalla prima affermazione che è true e ignora gli altri casi, ma tale comportamento potrebbe non essere quello desiderato.

Il punto di forza di C# è che è impossibile codificare gli switch in modo tale che sia il caso 1 che il caso 2 possano essere veri allo stesso tempo, quindi è garantita solo una risposta corretta.

+0

+1 per indicare che due etichette case non possono avere lo stesso valore –

0

Come tutti sappiamo, "Switches" espone una funzionalità simile a if. La maggior parte di noi (me compreso) lo vede come uno zucchero di sintassi - è più facile leggere un sacco di casi su un certo switch quindi leggere un numero di if/else se /.../ else. Ma il fatto è che l'interruttore non è zucchero sintassi.

Quello che devi capire è che il codice generato per switch (sia esso IL o codice macchina) non è lo stesso del codice generato per if sequenziali. Switch ha una buona ottimizzazione che, come già indicato da @Ed S., consente di funzionare in un tempo costante.

+3

Gli interruttori * possono * essere eseguiti in un tempo costante * a volte *. Gli switch non sono assolutamente ** garantiti ** per essere eseguiti in un tempo costante, e in pratica spesso non lo fanno. Esistono molti fattori che possono causare il passaggio di uno switch in tempo logaritmico o lineare. –

+0

@EricLippert, stavo guardando l'IL generato da un'istruzione switch e ho scoperto che le ottimizzazioni nella mia mente non sono realmente lì - ho iniziato a chiedermi cosa stava chiedendo l'OP - aveva senso. Posso suggerire un post sul blog su questo argomento? :) –

2

In generale, non ci sono punti di estensione nel compilatore Microsoft C# per quanto ne so (e anche Roslyn non ha intenzione di cambiarlo). Ma nulla ti impedisce di scrivere il tuo compilatore C# o, più realisticamente, di modificare l'open source Mono C# compiler.

In ogni caso, penso che sia molto più difficile di quello che vale.

Ma forse si può fare ciò che si desidera utilizzare qualcosa che è già nel linguaggio, vale a dire, le chiamate lambda e di metodo, per formare un “interruttore fluente”:

Switch.Value(true) 
    .Case(() => Foo(),() => Console.WriteLine("foo")) 
    .Case(() => Bar() == 2,() => Console.WriteLine("bar == 2")); 

Se non ti dispiace che tutti i valori delle condizioni saranno valutati ogni volta, lo si semplifica un po ':

Switch.Value(true) 
    .Case(Foo(),() => Console.WriteLine("foo")) 
    .Case(Bar() == 2,() => Console.WriteLine("bar == 2")); 
Problemi correlati