Ferramenta de depuração de “observação rápida” e expressões lambda do Visual Studio


96

5
Isso foi concluído e está disponível na prévia do VS 2015. visualstudio.uservoice.com/forums/121579-visual-studio/…
Francisco d'Anconia


Tentei um exemplo muito simples fornecido no MSDN para expressão lambda, mas não funciona. eu tenho o VS 2015 Enterprise Edition
Adeem

2
@Franciscod'Anconia para habilitar o suporte a lambda na depuração, "Usar modo de compatibilidade gerenciada" deve ser desmarcado ( stackoverflow.com/a/36559817/818321 ) Como resultado, você não poderá usar pontos de interrupção condicionais: blogs.msdn .microsoft.com / devops / 2013/10/16 /… e stackoverflow.com/a/35983978/818321
Nik

Respostas:


64

Expressões lambda, como métodos anônimos, são na verdade bestas muito complexas. Mesmo se excluirmos Expression(.NET 3.5), isso ainda deixa muita complexidade, não menos sendo variáveis ​​capturadas, que fundamentalmente re-estruturam o código que as usa (o que você pensa como variáveis ​​tornam-se campos em classes geradas pelo compilador) , com um pouco de fumaça e espelhos.

Como tal, não estou nem um pouco surpreso que você não possa usá-los ociosamente - há muito trabalho do compilador (e geração de tipo nos bastidores) que suporta essa mágica.


91

Não, você não pode usar expressões lambda na janela watch / locals / Instant. Como Marc apontou, isso é incrivelmente complexo. Eu queria mergulhar um pouco mais fundo no assunto.

O que a maioria das pessoas não considera ao executar uma função anônima no depurador é que ela não ocorre em um vácuo. O próprio ato de definir e executar uma função anônima muda a estrutura subjacente da base do código. Alterar o código, em geral, e em particular na janela imediata, é uma tarefa muito difícil.

Considere o seguinte código.

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

Este código específico cria um único fechamento para capturar o valor v1. A captura de fechamento é necessária sempre que uma função anônima usa uma variável declarada fora de seu escopo. Para todos os efeitos, v1 não existe mais nesta função. A última linha se parece mais com a seguinte

var v3 = closure1.v1 + v2;

Se a função Exemplo for executada no depurador, ela irá parar na linha Break. Agora imagine se o usuário digitou o seguinte na janela do relógio

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

Para executar isso corretamente, o depurador (ou mais apropriado o EE) precisaria criar um encerramento para a variável v2. Isso é difícil, mas não impossível de fazer.

O que realmente torna este um trabalho difícil para o EE é a última linha. Como essa linha deve ser executada agora? Para todos os efeitos, a função anônima excluiu a variável v2 e substituiu-a por closure2.v2. Então, a última linha de código realmente precisa ler

var v3 = closure1.v1 + closure2.v2;

No entanto, para realmente obter esse efeito no código, é necessário que o EE altere a última linha do código, que na verdade é uma ação ENC. Embora esse exemplo específico seja possível, uma boa parte dos cenários não é.

O que é ainda pior é a execução dessa expressão lambda não deve criar um novo encerramento. Na verdade, ele deve anexar dados ao fechamento original. Neste ponto, você vai direto para as limitações ENC.

Meu pequeno exemplo, infelizmente, apenas arranha a superfície dos problemas que encontramos. Eu continuo dizendo que vou escrever um post completo no blog sobre esse assunto e espero ter tempo neste fim de semana.


41
Lamente, lamente, aceite a mediocridade, lamente, lamente. O depurador é o coração do IDE, e você o quebrou! Lambdas na janela do relógio não precisam capturar nada. Como qualquer outro código de observação, eles fazem sentido apenas no quadro de pilha específico. (Ou então você captura a variável, muda para outra função com o mesmo nome de variável ... e o quê?) O depurador foi feito para hackear o compilador. Faça funcionar!
Aleksandr Dubinsky

2
Por que eles simplesmente não permitem variáveis ​​capturadas em lambdas na janela de observação. Simples e permitiria uma série de cenários de depuração onde lambdas estão apenas sendo usados ​​em um código verdadeiramente funcional.
Luiz Felipe

@LuizFelipe, mesmo isso ainda é um empreendimento massivo . Ele requer que o EE gere realmente o corpo da função completa para o retorno de chamada (todo o caminho para IL). O EE não faz nada desse tipo hoje, em vez disso, é um intérprete.
JaredPar

1
@JaredPar você pode compartilhar a postagem do blog marc falando sobre
Ehsan Sajjad

49

Você não pode usar expressões lambda nas janelas Immediate ou Watch.

No entanto, você pode usar expressões System.Linq.Dynamic , que assumem a forma .Where ("Id = @ 0", 2) - não tem toda a gama de métodos disponíveis no Linq padrão, e não tem todos os métodos poder das expressões lambda, mas ainda assim, é melhor do que nada!


2
Bom ... enquanto os outros explicaram enquanto não foi possível, este pelo menos nos fornece uma solução possível. +1
Nullius

1
Só para esclarecer, você "Importar System.Linq.Dynamic" e, em seguida, na janela de depuração, você escreve '"Where (something.AsQueryable," property> xyz ", nothing)'
smirkingman

Isso é ótimo. Mesmo que você não obtenha a gama completa de métodos de extensão do Linq, por exemplo, não há nenhum .Any(string predicate), você pode colocar algo como: .Where("Id>2").Any()na janela de observação ou Fixar no código-fonte. É ótimo!
Protetor um

22

O futuro chegou!

Suporte para depuração de expressões lambda foi adicionado ao Visual Studio 2015 ( visualização no momento da escrita).

O Expression Evaluator teve que ser reescrito, então muitos recursos estão faltando: depuração remota do ASP.NET, declarando variáveis ​​na janela Immediate, inspecionando variáveis ​​dinâmicas, etc. Além disso, expressões lambda que requerem chamadas para funções nativas não são suportadas atualmente.


É bom ver isso. Legal...!
Rahul Nikate



2

Expressões lambda não são suportadas pelo avaliador de expressão do depurador ... o que dificilmente é surpreendente, já que em tempo de compilação elas são usadas para criar métodos (ou Árvores de Expressão) em vez de expressões (dê uma olhada no Reflector com a tela mudada para .NET 2 para vê-los).

Além disso, é claro que eles poderiam formar um fechamento, outra camada inteira de estrutura.


Bem, eles podem criar métodos; eles podem criar Expressionárvores - depende do contexto.
Marc Gravell

1

No VS 2015 você pode fazer isso agora, este é um dos novos recursos que eles adicionaram.


1

Se você ainda precisa usar o Visual Studio 2013, pode escrever um loop ou expressão lambda na janela imediata usando também a janela do console do gerenciador de pacotes. No meu caso, adicionei uma lista no topo da função:

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

Onde minha GetAll()função é:

private DbSet<T> _dbSet;

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

    return list;
}

Aqui, recebo o seguinte erro, então queria imprimir todos os itens nos vários repositórios:

InnerException {"A instrução DELETE entrou em conflito com a restrição REFERENCE \" FK_dbo.Department_dbo.RoleHierarchy_OranizationalRoleId \ ". O conflito ocorreu no banco de dados \" CC_Portal_SchoolObjectModel \ ", tabela \" dbo.Department \ ", coluna 'OranizationalRoleId \". declaração foi encerrada. "} System.Exception {System.Data.SqlClient.SqlException}

Em seguida, descubro quantos registros estão no repositório do departamento, executando isto na janela imediata:

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

Que retornou 243.

Portanto, se você executar o seguinte no console do gerenciador de pacotes, ele imprimirá todos os itens:

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

O autor da ideia pode ser encontrado aqui


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.