Função local vs Lambda C # 7.0

178

Estou observando as novas implementações no C # 7.0 e acho interessante que elas tenham implementado funções locais, mas não consigo imaginar um cenário em que uma função local seja preferida a uma expressão lambda e qual é a diferença entre as duas.

Entendo que lambdas são anonymousfunções enquanto funções locais não, mas não consigo descobrir um cenário do mundo real, em que a função local tenha vantagens sobre as expressões lambda

Qualquer exemplo seria muito apreciado. Obrigado.

Sid
fonte
9
Genéricos, parâmetros de saída, funções recursivas sem ter de inicializar o lambda para nula, etc
Kirk Woll
5
@ KirkWoll - Você deve postar isso como resposta.
Enigmatividade

Respostas:

276

Isso foi explicado por Mads Torgersen no C # Design Meeting Notes, onde as funções locais foram discutidas pela primeira vez :

Você quer uma função auxiliar. Você o está usando apenas de uma única função e provavelmente usa variáveis ​​e parâmetros de tipo que estão no escopo nessa função que contém. Por outro lado, ao contrário de um lambda, você não precisa dele como um objeto de primeira classe, portanto, não deseja atribuir a ele um tipo de delegado e alocar um objeto de delegado real. Além disso, você pode querer que seja recursivo ou genérico, ou implementá-lo como um iterador.

Para expandir um pouco mais, as vantagens são:

  1. Atuação.

    Ao criar um lambda, é necessário criar um delegado, que é uma alocação desnecessária neste caso. Funções locais são realmente apenas funções, sem necessidade de delegados.

    Além disso, as funções locais são mais eficientes na captura de variáveis ​​locais: lambdas geralmente capturam variáveis ​​em uma classe, enquanto as funções locais podem usar uma estrutura (aprovada usando ref), o que evita novamente uma alocação.

    Isso também significa que chamar funções locais é mais barato e elas podem ser incorporadas, possivelmente aumentando ainda mais o desempenho.

  2. As funções locais podem ser recursivas.

    Os lambdas também podem ser recursivos, mas requerem código estranho, onde você primeiro atribui nulla uma variável delegada e depois ao lambda. Funções locais podem naturalmente ser recursivas (incluindo recursivas mutuamente).

  3. As funções locais podem ser genéricas.

    As lambdas não podem ser genéricas, pois precisam ser atribuídas a uma variável com um tipo concreto (esse tipo pode usar variáveis ​​genéricas do escopo externo, mas isso não é a mesma coisa).

  4. Funções locais podem ser implementadas como um iterador.

    Lambdas não pode usar a palavra-chave yield return(e yield break) para implementar a IEnumerable<T>função -returning. Funções locais podem.

  5. As funções locais parecem melhores.

    Isso não é mencionado na citação acima e pode ser apenas meu viés pessoal, mas acho que a sintaxe da função normal parece melhor do que atribuir um lambda a uma variável delegada. As funções locais também são mais sucintas.

    Comparar:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
svick
fonte
22
Gostaria de acrescentar que as funções locais têm nomes de parâmetros no lado do chamador. Lambdas não.
precisa saber é o seguinte
3
@Lensflare É verdade que os nomes de parâmetros das lambdas não são preservados, mas é porque eles precisam ser convertidos em delegados, que têm seus próprios nomes. Por exemplo: Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
precisa
1
Ótima lista! No entanto, posso imaginar como o compilador IL / JIT poderia executar todas as otimizações mencionadas em 1. também para delegados se o uso deles seguir algumas regras.
Marcin Kaczmarek
1
@Casebash Porque lambdas sempre usam um delegado e esse delegado mantém o fechamento como um object. Portanto, as lambdas poderiam usar uma estrutura, mas precisariam ser encaixotadas, para que você ainda tivesse essa alocação adicional.
svick
1
@happybits Principalmente quando você não precisa dar um nome a ele, como quando você está passando para o método.
svick
83

Além da grande resposta de svick, há mais uma vantagem nas funções locais:
elas podem ser definidas em qualquer lugar da função, mesmo após a returndeclaração.

public double DoMath(double a, double b)
{
    var resultA = f(a);
    var resultB = f(b);
    return resultA + resultB;

    double f(double x) => 5 * x + 3;
}
Tim Pohlmann
fonte
5
Isso é realmente útil, pois eu posso me acostumar a colocar todas as funções auxiliares em um #region Helpersna parte inferior da função, para evitar desorganização nessa função e espicamente evitar desorganização na classe principal.
AustinWBryan
Eu também aprecio isso. Isso torna a função principal que você está vendo mais fácil de ler, pois você não precisa procurar ao redor para descobrir onde começa. Se você quiser ver os detalhes da implementação, continue olhando para o final.
Remi Despres-Smyth
3
se suas funções são tão grandes que precisam de regiões, são grandes demais.
ssmith
9

Se você também quiser saber como testar a função local, verifique o JustMock, pois ele possui a funcionalidade para fazê-lo. Aqui está um exemplo simples de classe que será testado:

public class Foo // the class under test
{ 
    public int GetResult() 
    { 
        return 100 + GetLocal(); 
        int GetLocal () 
        { 
            return 42; 
        } 
    } 
}

E aqui está a aparência do teste:

[TestClass] 
public class MockLocalFunctions 
{ 
    [TestMethod] 
    public void BasicUsage() 
    { 
        //Arrange 
        var foo = Mock.Create<Foo>(Behavior.CallOriginal); 
        Mock.Local.Function.Arrange<int>(foo, "GetResult", "GetLocal").DoNothing(); 

        //Act 
        var result = foo. GetResult(); 

        //Assert 
        Assert.AreEqual(100, result); 
    } 
} 

Aqui está um link para a documentação do JustMock .

Aviso Legal. Eu sou um dos desenvolvedores responsáveis ​​pelo JustMock .

Mihail Vladov
fonte
é ótimo ver desenvolvedores apaixonados advogando que as pessoas usem sua ferramenta. Como você se interessou em escrever ferramentas de desenvolvedor como um emprego em período integral? Como americano, minha impressão é que pode ser difícil encontrar carreiras desse tipo, a menos que você tenha mestrado ou doutorado. em comp sci.
John Zabroski 01/03/19
Olá John e obrigado pelas amáveis ​​palavras. Como desenvolvedor de software, não vejo nada melhor do que ser apreciado pelos meus clientes pelo valor que forneço a eles. Combine isso com o desejo de um trabalho desafiador e competitivo e você receberá uma lista bastante limitada de coisas pelas quais eu adoraria. A criação de ferramentas de desenvolvedor de produtividade está nessa lista. Pelo menos em minha mente :) Com relação à carreira, acho que as empresas que fornecem ferramentas de desenvolvedor representam uma porcentagem muito pequena de todas as empresas de software e é por isso que é mais difícil encontrar essa oportunidade.
Mihail Vladov
Uma pergunta separada. Por que você não liga para o VerifyAll aqui? Existe uma maneira de dizer ao JustMock para verificar se a função local também foi chamada?
John Zabroski 8/03/19
2
Olá @JohnZabroski, o cenário testado não exigia a afirmação de ocorrências. Obviamente, você pode verificar se foi feita uma ligação. Primeiro, você precisa especificar por quanto tempo espera que o método seja chamado. Assim: .DoNothing().OccursOnce();e depois afirme que a chamada foi feita chamando o Mock.Assert(foo);método Se você estiver interessado em saber como outros cenários são suportados, leia o artigo de ajuda Asserting Occurrence .
Mihail Vladov
0

Uso funções in-line para evitar a pressão da coleta de lixo, especialmente quando se lida com métodos de execução mais longos. Digamos que você queira obter 2 anos ou dados de mercado para um determinado símbolo. Além disso, é possível incluir muitas funcionalidades e lógica de negócios, se necessário.

o que se faz é abrir uma conexão de soquete ao servidor e fazer um loop sobre os dados que vinculam um evento a um evento. Pode-se pensar da mesma maneira que uma classe é projetada, apenas uma não está escrevendo métodos auxiliares em todo o lugar que realmente estão trabalhando apenas para uma peça de funcionalidade. Abaixo está um exemplo de como isso pode parecer, observe que estou usando variáveis ​​e os métodos "helper" estão abaixo do finalmente. No Finalmente, removo bem os manipuladores de eventos, se minha classe do Exchange fosse externa / injetada, não haveria nenhum manipulador de eventos pendente registrado

void List<HistoricalData> RequestData(Ticker ticker, TimeSpan timeout)
{
    var socket= new Exchange(ticker);
    bool done=false;
    socket.OnData += _onData;
    socket.OnDone += _onDone;
    var request= NextRequestNr();
    var result = new List<HistoricalData>();
    var start= DateTime.Now;
    socket.RequestHistoricalData(requestId:request:days:1);
    try
    {
      while(!done)
      {   //stop when take to long….
        if((DateTime.Now-start)>timeout)
           break;
      }
      return result;

    }finally
    {
        socket.OnData-=_onData;
        socket.OnDone-= _onDone;
    }


   void _OnData(object sender, HistoricalData data)
   {
       _result.Add(data);
   }
   void _onDone(object sender, EndEventArgs args)
   {
      if(args.ReqId==request )
         done=true;
   } 
}

Você pode ver as vantagens mencionadas abaixo, aqui você pode ver uma implementação de amostra. Espero que ajude a explicar os benefícios.

Walter Vehoeven
fonte
2
1. Esse é um exemplo e explicação realmente complexo, apenas para demonstrar funções locais. 2. As funções locais não evitam alocações quando comparadas às lambdas neste exemplo, porque ainda precisam ser convertidas em delegados. Portanto, não vejo como eles evitariam o GC.
svick
1
não passando / copiando variáveis, a resposta do svick cobre o resto muito bem. Não há necessidade de duplicar a sua resposta
Walter Vehoeven