2010-02-16 6 views
5

Chiamando Push() e Pop() un'istanza di Stack<T> in una singola riga ottengo un comportamento diverso rispetto all'esecuzione dello stesso codice imho in due righe.Ordine di operazione imprevisto nello stackrelativo a un rivestimento

Il seguente frammento di codice riproduce il comportamento:

static void Main(string[] args) 
{ 
Stack<Element> stack = new Stack<Element>(); 
Element e1 = new Element { Value = "one" }; 
Element e2 = new Element { Value = "two" }; 
stack.Push(e1); 
stack.Push(e2); 

Expected(stack); // element on satck has value "two" 
//Unexpected(stack); // element on stack has value "one" 

Console.WriteLine(stack.Peek().Value); 
Console.ReadLine(); 
} 

public static void Unexpected(Stack<Element> stack) 
{ 
stack.Peek().Value = stack.Pop().Value; 
} 

public static void Expected(Stack<Element> stack) 
{ 
Element e = stack.Pop(); 
stack.Peek().Value = e.Value; 
} 

La classe Element è davvero fondamentale:

public class Element 
{ 
public string Value 
{ 
    get; 
    set; 
} 
} 

Con questo codice ottengo il seguente risultato (.NET 3.5, Win 7, completamente patchato):

  • Calling Expected() (versione con due linee) lea ves un elemento nello stack con Value impostato su "two".
  • Quando si chiama Unexpected() (Versione con una linea) ottengo un elemento sul risma al Value insieme a "one".

L'unica ragione della differenza che potevo immaginare era la precedenza dell'operatore. Poiché l'operatore di assegnazione (=) ha il lowest precedence, non vedo alcun motivo per cui i due metodi dovrebbero comportarsi in modo diverso.

Ho anche avuto uno sguardo alla IL generato:

.method public hidebysig static void Unexpected(class [System]System.Collections.Generic.Stack`1<class OperationOrder.Element> stack) cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: callvirt instance !0 [System]System.Collections.Generic.Stack`1<class OperationOrder.Element>::Peek() 
    L_0006: ldarg.0 
    L_0007: callvirt instance !0 [System]System.Collections.Generic.Stack`1<class OperationOrder.Element>::Pop() 
    L_000c: callvirt instance string OperationOrder.Element::get_Value() 
    L_0011: callvirt instance void OperationOrder.Element::set_Value(string) 
    L_0016: ret 
} 

Io non sono un crepa IL, ma per me questo codice sembra ancora buono ans dovrebbero lasciare un elemento sullo stack con valore impostato su " Due".

Qualcuno mi può spiegare il motivo per cui il metodo Unexpected() fa qualcosa di diverso rispetto Expected()?

Grazie mille!

Lukas

risposta

9

In C# gli operandi vengono valutati da sinistra a destra. Sempre sempre sempre da sinistra a destra. Pertanto gli operandi dell'operatore = vengono valutati da sinistra a destra. Nell'esempio "previsto", l'espressione Pop() si verifica in un'istruzione eseguita prima dell'istruzione dell'espressione Peek(). Nel tuo esempio "inatteso", l'espressione Peek() si trova a sinistra dell'espressione Pop(), quindi viene valutata per prima.

Risposta SLaks nota che il destinatario di una chiamata viene sempre valutato prima degli argomenti della chiamata. Questo è corretto, perché il destinatario di una chiamata è sempre a sinistra degli argomenti! Ma l'affermazione di SLaks che questo ha qualcosa a che fare con il fatto che sia un setter di proprietà non è corretto.Otterrai esattamente lo stesso comportamento se Value fosse un campo; l'espressione che contiene l'accesso al campo si trova a sinistra del valore assegnato e pertanto viene calcolata per prima.

Hai menzionato "precedenza", che indica che probabilmente sottoscrivi la nozione completamente mitica e assolutamente falsa che la precedenza ha qualcosa a che fare con l'ordine di esecuzione. Non. Spogliati della tua fede in questo mito. L'ordine di esecuzione delle sottoespressioni è da sinistra a destra. Il funzionamento degli operatori è fatto in ordine di precedenza.

Ad esempio, considerare F() + G() * H(). * ha una precedenza più alta di + Nel tuo mondo mitico l'operazione di precedenza più alta viene eseguita per prima, quindi G() viene valutato, quindi H(), quindi vengono moltiplicati, quindi F(), quindi l'aggiunta.

Questo è completamente e assolutamente sbagliato. Dillo con me: la precedenza non ha nulla a che fare con l'ordine di esecuzione. Le sottoespressioni sono valutate da sinistra a destra, quindi prima valutiamo F(), poi G(), poi H(). Quindi calcoliamo il prodotto del risultato di G() e H(). Quindi calcoliamo la somma del prodotto con il risultato di F(). Cioè, questa espressione è equivalente a:

temp1 = F(); 
temp2 = G(); 
temp3 = H(); 
temp4 = temp2 * temp3; 
result = temp1 + temp4; 

L'operatore = è un operatore come qualsiasi altro; un operatore con precedenza bassa, ma un operatore. I suoi operandi vengono valutati da sinistra a destra, e poiché si tratta di un operatore con precedenza bassa, l'effetto dell'operatore - l'assegnazione - viene eseguito più tardi degli effetti di tutti gli altri operatori. L'effetto dell'operatore e il calcolo dei suoi operandi sono cose completamente diverse. Il primo è fatto in ordine di precedenza. Quest'ultimo è fatto nell'ordine da sinistra a destra.

È chiaro?

AGGIORNAMENTO: Confusione di precedenza, associatività e ordine di esecuzione è estremamente comune; molti autori di libri con una lunga esperienza nel design del linguaggio di programmazione sbagliano. C# ha regole molto rigide che definiscono ciascuna; se siete interessati a maggiori dettagli su come funziona tutto questo, si potrebbe essere interessati a questi articoli che ho scritto su questo argomento:

http://blogs.msdn.com/ericlippert/archive/tags/precedence/default.aspx

+0

Non c'è '= 'zucchero sintattico per il setter di proprietà qui, e non un operatore? – SLaks

+0

Mi rendo conto che il comportamento sarebbe lo stesso per un normale compito (campo o locale), ma volevo collegarmi alla parte corretta della specifica. – SLaks

+0

Perfettamente chiaro ora. E lo ricorderò - lo prometto! –

Problemi correlati