Visual Studio debug dello strumento "quick watch" e delle espressioni lambda


96

5
Questo è stato completato ed è disponibile nell'anteprima di VS 2015. visualstudio.uservoice.com/forums/121579-visual-studio/…
Francisco d'Anconia


Ho provato un esempio molto semplice fornito su MSDN per l'espressione lambda ma non funziona. ho VS 2015 Enterprise Edition
Adeem

2
@ Franciscod'Anconia per abilitare il supporto lambda nel debug, "Usa modalità di compatibilità gestita" deve essere spuntato ( stackoverflow.com/a/36559817/818321 ) Di conseguenza, non sarai in grado di utilizzare punti di interruzione condizionali: blogs.msdn .microsoft.com / devops / 2013/10/16 /… e stackoverflow.com/a/35983978/818321
Nik

Risposte:


64

Le espressioni lambda, come i metodi anonimi, sono in realtà bestie molto complesse. Anche se escludiamo Expression(.NET 3.5), ciò lascia ancora molta complessità, non ultimo l'essere variabili catturate, che fondamentalmente ri-strutturano il codice che le utilizza (ciò che si pensa come variabili diventano campi su classi generate dal compilatore) , con un po 'di fumo e specchi.

In quanto tale, non sono per nulla sorpreso che tu non possa usarli pigramente: c'è molto lavoro del compilatore (e la generazione di tipi dietro le quinte) che supporta questa magia.


91

No, non puoi usare espressioni lambda nella finestra watch / locals / immediate. Come ha sottolineato Marc, questo è incredibilmente complesso. Tuttavia, volevo approfondire l'argomento.

Ciò che la maggior parte delle persone non considera nell'esecuzione di una funzione anonima nel debugger è che non si verifica in un vuoto. L'atto stesso di definire ed eseguire una funzione anonima cambia la struttura sottostante della base di codice. Cambiare il codice, in generale, e in particolare dalla finestra immediata, è un compito molto difficile.

Considera il codice seguente.

void Example() {
  var v1 = 42;
  var v2 = 56; 
  Func<int> func1 = () => v1;
  System.Diagnostics.Debugger.Break();
  var v3 = v1 + v2;
}

Questo particolare codice crea una singola chiusura per acquisire il valore v1. L'acquisizione della chiusura è richiesta ogni volta che una funzione anonima utilizza una variabile dichiarata al di fuori del suo ambito. A tutti gli effetti, la v1 non esiste più in questa funzione. L'ultima riga in realtà è più simile alla seguente

var v3 = closure1.v1 + v2;

Se la funzione Example viene eseguita nel debugger, si fermerà alla riga di interruzione. Ora immagina se l'utente ha digitato quanto segue nella finestra di controllo

(Func<int>)(() => v2);

Per eseguire correttamente ciò, il debugger (o più appropriato l'EE) dovrebbe creare una chiusura per la variabile v2. Questo è difficile ma non impossibile.

Ciò che rende davvero questo un lavoro duro per l'EE è l'ultima riga. Come dovrebbe essere eseguita quella linea ora? A tutti gli effetti la funzione anonima ha cancellato la variabile v2 e l'ha sostituita con closing2.v2. Quindi l'ultima riga di codice ora ha davvero bisogno di essere letta

var v3 = closure1.v1 + closure2.v2;

Tuttavia, per ottenere effettivamente questo effetto nel codice è necessario che l'EE modifichi l'ultima riga di codice che è in realtà un'azione ENC. Sebbene questo esempio specifico sia possibile, una buona parte degli scenari non lo è.

La cosa peggiore è che l'esecuzione dell'espressione lambda non dovrebbe creare una nuova chiusura. Dovrebbe effettivamente aggiungere dati alla chiusura originale. A questo punto corri dritto nelle limitazioni ENC.

Il mio piccolo esempio purtroppo graffia solo la superficie dei problemi in cui ci imbattiamo. Continuo a dire che scriverò un intero post sul blog su questo argomento e spero di avere tempo questo fine settimana.


41
Piagnucolare, lamentarsi, accettare la mediocrità, lamentarsi, lamentarsi. Il debugger è il cuore dell'IDE e l'hai rotto! I Lambda nella finestra di controllo non devono acquisire nulla. Come qualsiasi altro codice di controllo, hanno senso solo per il particolare stack frame. (Oppure si cattura la variabile, si passa a un'altra funzione con lo stesso nome di variabile ... e cosa?) Il debugger ha lo scopo di hackerare il compilatore. Fallo funzionare!
Aleksandr Dubinsky

2
Perché semplicemente non consentono variabili acquisite su lambda nella finestra di controllo. Semplice e consentirebbe una serie di scenari di debug in cui i lambda vengono utilizzati solo in codice veramente funzionale.
Luiz Felipe

@LuizFelipe anche questo è ancora un impegno enorme . Richiede che l'EE generi effettivamente il corpo completo della funzione per la richiamata (fino a IL). L'EE oggi non fa nulla di questo genere, invece è un interprete.
JaredPar

1
@JaredPar puoi condividere il post sul blog di cui parla Marc
Ehsan Sajjad,

49

Non è possibile utilizzare espressioni lambda nelle finestre Immediate o Watch.

È tuttavia possibile utilizzare le espressioni System.Linq.Dynamic , che assumono la forma .Where ("Id = @ 0", 2) - non ha l'intera gamma di metodi disponibili in Linq standard e non ha la piena potere delle espressioni lambda, ma comunque, è meglio di niente!


2
Bene ... mentre gli altri hanno spiegato mentre non era possibile, questo almeno ci fornisce una possibile soluzione. +1
Nullius

1
Giusto per chiarire, "Importa System.Linq.Dynamic" e poi nella finestra di debug scrivi '"Where (something.AsQueryable," property> xyz ", nothing)'
smirkingman

Questo è fantastico. Anche se non ottieni l'intera gamma di metodi di estensione Linq, ad esempio non ce n'è .Any(string predicate), puoi inserire qualcosa come: .Where("Id>2").Any()nella finestra di controllo o Aggiungi a sorgente. È ottimo!
Protettore uno

22

Il futuro è arrivato!

Il supporto per il debug delle espressioni lambda è stato aggiunto a Visual Studio 2015 ( anteprima al momento della scrittura).

Expression Evaluator ha dovuto essere riscritto, quindi mancano molte funzionalità: debug remoto di ASP.NET, dichiarazione di variabili nella finestra immediata, ispezione di variabili dinamiche, ecc. Anche le espressioni lambda che richiedono chiamate a funzioni native non sono attualmente supportate.


È bello vedere. Freddo...!
Rahul Nikate



2

Le espressioni Lambda non sono supportate dall'analizzatore di espressioni del debugger ... il che non sorprende poiché in fase di compilazione vengono utilizzate per creare metodi (o alberi delle espressioni) piuttosto che espressioni (dai un'occhiata in Reflector con il display impostato su .NET 2 per guardali).

Inoltre, ovviamente, potrebbero formare una chiusura, un altro intero strato di struttura.


Ebbene, potrebbero creare metodi; potrebbero creare Expressionalberi - dipende dal contesto.
Marc Gravell

1

In VS 2015 puoi farlo ora, questa è una delle nuove funzionalità che hanno aggiunto.


1

Se è ancora necessario utilizzare Visual Studio 2013, è possibile effettivamente scrivere un ciclo o un'espressione lambda nella finestra immediata utilizzando anche la finestra della console del gestore pacchetti. Nel mio caso, ho aggiunto un elenco all'inizio della funzione:

private void RemoveRoleHierarchy()
{
    #if DEBUG
    var departments = _unitOfWork.DepartmentRepository.GetAll().ToList();
    var roleHierarchies = _unitOfWork.RoleHierarchyRepository.GetAll().ToList();
    #endif

    try
    {
        //RoleHierarchy
        foreach (SchoolBo.RoleHierarchy item in _listSoRoleHierarchy.Where(r => r.BusinessKeyMatched == false))
            _unitOfWork.RoleHierarchyRepository.Remove(item.Id);

        _unitOfWork.Save();
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.ToString());
        throw;
    }
}

Dov'è la mia GetAll()funzione:

private DbSet<T> _dbSet;

public virtual IList<T> GetAll()
{
    List<T> list;
    IQueryable<T> dbQuery = _dbSet;
    list = dbQuery
        .ToList<T>();

    return list;
}

Qui continuavo a ricevere il seguente errore, quindi volevo stampare tutti gli elementi nei vari repository:

InnerException {"L'istruzione DELETE è in conflitto con il vincolo REFERENCE \" FK_dbo.Department_dbo.RoleHierarchy_OranizationalRoleId \ ". Il conflitto si è verificato nel database \" CC_Portal_SchoolObjectModel \ ", tabella \" dbo.Department \ ", colonna 'OranizationalRoleId \ nThe. \ R l'istruzione è stata terminata. "} System.Exception {System.Data.SqlClient.SqlException}

Quindi, scopro quanti record ci sono nel repository del dipartimento eseguendolo nella finestra immediata:

_unitOfWork.DepartmentRepository.GetAll().ToList().Count

Che ha restituito 243.

Quindi, se esegui quanto segue nella console del gestore pacchetti, stampa tutti gli elementi:

PM> for($i = 0; $i -lt 243; $i++) { $a = $dte.Debugger.GetExpression("departments[$i].OrgagnizationalRoleId"); Write-Host $a.Value $i }

L'autore dell'idea può essere trovato qui


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.