O que é um fechamento ? Nós os temos no .NET?
Se eles existirem no .NET, você poderia fornecer um trecho de código (de preferência em C #) explicando-o?
O que é um fechamento ? Nós os temos no .NET?
Se eles existirem no .NET, você poderia fornecer um trecho de código (de preferência em C #) explicando-o?
Respostas:
Eu tenho um artigo sobre esse mesmo tópico . (Ele tem muitos exemplos.)
Em essência, um fechamento é um bloco de código que pode ser executado posteriormente, mas que mantém o ambiente em que foi criado - ou seja, ele ainda pode usar as variáveis locais etc. do método que o criou, mesmo depois disso. O método concluiu a execução.
O recurso geral de fechamentos é implementado em C # por métodos anônimos e expressões lambda.
Aqui está um exemplo usando um método anônimo:
using System;
class Test
{
static void Main()
{
Action action = CreateAction();
action();
action();
}
static Action CreateAction()
{
int counter = 0;
return delegate
{
// Yes, it could be done in one statement;
// but it is clearer like this.
counter++;
Console.WriteLine("counter={0}", counter);
};
}
}
Resultado:
counter=1
counter=2
Aqui podemos ver que a ação retornada por CreateAction ainda tem acesso à variável counter e pode realmente incrementá-la, mesmo que o próprio CreateAction tenha terminado.
counter
está disponível para ser incrementado - o compilador gera uma classe que contém um counter
campo e qualquer código referente a ele counter
acaba passando por uma instância dessa classe.
Se você estiver interessado em ver como o C # implementa o Closure, leia "Conheço a resposta (seu 42) blog"
O compilador gera uma classe em segundo plano para encapsular o método anoymous e a variável j
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
para a função:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Transformando-o em:
private static void fillFunc(int count)
{
for (int i = 0; i < count; i++)
{
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.j = i;
Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
}
}
Encerramentos são valores funcionais que mantêm valores variáveis de seu escopo original. O C # pode usá-los na forma de delegados anônimos.
Para um exemplo muito simples, use este código C #:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
No final, bar será definido como 4, e o delegado myClosure poderá ser distribuído para ser usado em outras partes do programa.
Os closures podem ser usados para muitas coisas úteis, como execução atrasada ou para simplificar interfaces - o LINQ é construído principalmente usando closures. A maneira mais imediata de ser útil para a maioria dos desenvolvedores é adicionar manipuladores de eventos a controles criados dinamicamente - você pode usar fechamentos para adicionar comportamento quando o controle é instanciado, em vez de armazenar dados em outro local.
Func<int, int> GetMultiplier(int a)
{
return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2)); //outputs 4
Console.WriteLine(fn2(3)); //outputs 6
Console.WriteLine(fn3(2)); //outputs 6
Console.WriteLine(fn3(3)); //outputs 9
Um fechamento é uma função anônima passada fora da função em que é criada. Ele mantém quaisquer variáveis da função em que é criado e que usa.
Aqui está um exemplo artificial para C # que eu criei a partir de código semelhante em JavaScript:
public delegate T Iterator<T>() where T : class;
public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
var i = 0;
return delegate { return (i < x.Count) ? x[i++] : null; };
}
Então, aqui está um código que mostra como usar o código acima ...
var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});
// So, although CreateIterator() has been called and returned, the variable
// "i" within CreateIterator() will live on because of a closure created
// within that method, so that every time the anonymous delegate returned
// from it is called (by calling iterator()) it's value will increment.
string currentString;
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null
Espero que seja um pouco útil.
Basicamente, o fechamento é um bloco de código que você pode transmitir como argumento para uma função. O C # suporta fechamentos na forma de delegados anônimos.
Aqui está um exemplo simples: o
método List.Find pode aceitar e executar trechos de código (fechamento) para encontrar o item da lista.
// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });
Usando a sintaxe C # 3.0, podemos escrever isso como:
ints.Find(value => value == 1);
Um fechamento é quando uma função é definida dentro de outra função (ou método) e usa as variáveis do método pai . Esse uso de variáveis localizadas em um método e envolvidas em uma função definida dentro dele é chamado de fechamento.
Mark Seemann tem alguns exemplos interessantes de fechamentos em seu blog, onde ele faz um paralelo entre oop e programação funcional.
E para torná-lo mais detalhado
var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
{
var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
return File.ReadAllText(path);
};//the entire process is called a closure.
Closures são blocos de código que referenciam uma variável fora de si mesmos (abaixo deles na pilha), que podem ser chamados ou executados mais tarde (como quando um evento ou delegado é definido e podem ser chamados em algum momento futuro indefinido) ) ... Como a variável externa à qual o pedaço de código faz referência pode ficar fora de escopo (e de outra forma teria sido perdida), o fato de ser referenciada pelo pedaço de código (chamado de fechamento) diz ao tempo de execução para "manter "essa variável no escopo até que não seja mais necessária pelo pedaço de código de fechamento ...
Eu tenho tentado entender também, bem abaixo estão os trechos de código para o mesmo código em Javascript e C # mostrando o fechamento.
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
var b = c();
<body>
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="b()"/>
</body>
C #:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
JavaScript:
var c = function ()
{
var d = 0;
function inner() {
d++;
alert(d);
}
return inner;
};
var a = c();
<input type=button value=call onClick="a()"/>
<input type=button value=call onClick="a()"/>
C #:
using System.IO;
using System;
class Program
{
static void Main()
{
var a = new a();
var b = new a();
a.call();
a.call();
a.call();
b.call();
b.call();
b.call();
}
}
public class a {
static int b = 0;
public void call()
{
b++;
Console.WriteLine(b);
}
}
Do nada, uma resposta simples e mais compreensiva do resumo nutricional do livro C # 7.0.
Pré-requisito que você deve saber : Uma expressão lambda pode fazer referência às variáveis e parâmetros locais do método em que está definida (variáveis externas).
static void Main()
{
int factor = 2;
//Here factor is the variable that takes part in lambda expression.
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}
Parte real : variáveis externas referenciadas por uma expressão lambda são chamadas de variáveis capturadas. Uma expressão lambda que captura variáveis é chamada de fechamento.
Último ponto a ser observado : As variáveis capturadas são avaliadas quando o delegado é realmente chamado, não quando as variáveis foram capturadas:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Se você escrever um método anônimo em linha (C # 2) ou (preferencialmente) uma expressão Lambda (C # 3 +), um método real ainda estará sendo criado. Se esse código estiver usando uma variável local de escopo externo - você ainda precisará passar essa variável para o método de alguma forma.
por exemplo, use esta cláusula Linq Where (que é um método de extensão simples que transmite uma expressão lambda):
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time
{
i++;
return true;
});
se você quiser usar i nessa expressão lambda, precisará passá-la para o método criado.
Portanto, a primeira pergunta que se coloca é: deve ser passada por valor ou referência?
Passar por referência é (eu acho) mais preferível quando você obtém acesso de leitura / gravação a essa variável (e é isso que o C # faz; acho que a equipe da Microsoft pesou os prós e contras e seguiu com referência); de acordo com Jon Skeet artigo , Java foi com valor).
Mas então surge outra pergunta: onde alocar esse i?
Deveria realmente / naturalmente ser alocado na pilha? Bem, se você alocá-lo na pilha e passá-lo por referência, pode haver situações em que ela sobrevive ao seu próprio quadro de pilha. Veja este exemplo:
static void Main(string[] args)
{
Outlive();
var list = whereItems.ToList();
Console.ReadLine();
}
static IEnumerable<string> whereItems;
static void Outlive()
{
var i = 0;
var items = new List<string>
{
"Hello","World"
};
whereItems = items.Where(x =>
{
i++;
Console.WriteLine(i);
return true;
});
}
A expressão lambda (na cláusula Where) cria novamente um método que se refere a um i. Se i estiver alocado na pilha do Outlive, quando você enumerar os whereItems, o i usado no método gerado apontará para o i do Outlive, ou seja, para um local na pilha que não está mais acessível.
Ok, então precisamos disso na pilha então.
Então, o que o compilador C # faz para oferecer suporte a esse anônimo / lambda embutido é usar o que é chamado " Closures ": Ele cria uma classe no Heap chamada ( bastante ) DisplayClass que possui um campo que contém i e a Function que realmente usa isto.
Algo que seria equivalente a isso (você pode ver a IL gerada usando ILSpy ou ILDASM):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Instancia essa classe no seu escopo local e substitui qualquer código relacionado a i ou à expressão lambda por essa instância de fechamento. Então - sempre que você estiver usando o i no seu código de "escopo local" onde eu fui definido, você estará realmente usando esse campo de instância DisplayClass.
Portanto, se eu mudasse o "local" i no método principal, ele realmente mudará _DisplayClass.i;
ie
var i = 0;
var items = new List<string>
{
"Hello","World"
};
var filtered = items.Where(x =>
{
i++;
return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10; // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12
ele imprimirá 12, quando "i = 10" for para esse campo de distribuição e o alterar imediatamente antes da segunda enumeração.
Uma boa fonte sobre o assunto é este módulo Bart De Smet Pluralsight (requer registro) (também ignora seu uso incorreto do termo "Elevação" - o que (eu acho) ele quer dizer é que a variável local (ie i) é alterada para se referir para o novo campo DisplayClass).
Em outras notícias, parece haver alguma concepção errônea de que "Closures" estão relacionados a loops - como eu entendo "Closures" NÃO são um conceito relacionado a loops , mas sim a métodos anônimos / expressões lambda que usam variáveis locais com escopo - embora alguns truques as perguntas usam loops para demonstrá-lo.
Um fechamento é uma função, definida dentro de uma função, que pode acessar as variáveis locais e seu pai.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
então a função dentro do método find.
t => t.Name == name
pode acessar as variáveis dentro de seu escopo, t, e o nome da variável que está no escopo de seus pais. Mesmo que seja executado pelo método find como um delegado, de outro escopo todos juntos.