Bir nedir kapatma ? .NET'te var mı?
.NET'te mevcutlarsa, lütfen açıklayan bir kod snippet'i (tercihen C # olarak) sağlayabilir misiniz?
Bir nedir kapatma ? .NET'te var mı?
.NET'te mevcutlarsa, lütfen açıklayan bir kod snippet'i (tercihen C # olarak) sağlayabilir misiniz?
Yanıtlar:
Ben çok bu konuda bir makale . (Çok sayıda örneği var.)
Özünde, bir kapanış, daha sonraki bir zamanda yürütülebilen, ancak ilk oluşturulduğu ortamı koruyan bir kod bloğudur - yani, onu oluşturan yöntemin yerel değişkenlerini vb. yönteminin yürütülmesi tamamlandı.
Kapakların genel özelliği, C # 'da anonim yöntemler ve lambda ifadeleriyle uygulanır.
Anonim bir yöntem kullanan bir örnek:
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);
};
}
}
Çıktı:
counter=1
counter=2
Burada, CreateAction tarafından döndürülen eylemin hala sayaç değişkenine erişimi olduğunu ve CreateAction'ın kendisi bitmiş olsa bile gerçekten artırabilir.
counter
artırılmak için kullanılabilir - derleyici bir counter
alan içeren bir sınıf oluşturur ve counter
bu sınıfın bir örneğinden geçerek sonuçlanan herhangi bir kod oluşturur.
C # 'ın Closure'u nasıl uyguladığını görmekle ilgileniyorsanız "Cevabı biliyorum (42) blogu"
Derleyici, anoymous yöntemi ve j değişkenini kapsüllemek için arka planda bir sınıf oluşturur
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
fonksiyon için:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
Şu hale getiriliyor:
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);
}
}
Kapaklar, orijinal kapsamlarından değişken değerlere dayanan fonksiyonel değerlerdir. C # onları anonim delegeler şeklinde kullanabilir.
Çok basit bir örnek için şu C # kodunu alın:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
Sonunda, çubuk 4'e ayarlanacak ve myClosure delegesi programın başka bir yerinde kullanılmak üzere etrafından geçirilebilir.
Kapaklar, gecikmeli yürütme veya arayüzleri basitleştirmek için birçok yararlı şey için kullanılabilir - LINQ esas olarak kapaklar kullanılarak oluşturulur. Çoğu geliştirici için kullanışlı olmanın en acil yolu, dinamik olarak oluşturulan denetimlere olay işleyicileri eklemektir - başka bir yerde veri depolamak yerine denetim başlatıldığında davranış eklemek için kapanışları kullanabilirsiniz.
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
Kapatma, oluşturulduğu işlevin dışına iletilen anonim bir işlevdir. Kullanıldığı işlevdeki tüm değişkenleri korur.
JavaScript'te benzer koddan oluşturduğum C # için yapmacık bir örnek:
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; };
}
Yani, yukarıdaki kodun nasıl kullanılacağını gösteren bazı kodlar ...
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
Umarım biraz yardımcı olur.
Kapanışlar, daha sonra çağrılabilen veya çalıştırılabilen (bir olay veya delege tanımlandığında olduğu gibi ve belirsiz bir süre sonra çağrılabilen), kendilerinin dışındaki bir değişkeni (altta yığının altından) referans alan kod parçalarıdır. ) ... Kod referanslarının yığınının dış değişkeni kapsam dışına çıkabileceği (ve aksi takdirde kaybolacağı) olduğundan, kod yığınına (kapanış denir) başvuruda bulunulması, çalışma zamanına "tutma" "kodun kapanış yığınına artık ihtiyaç duyulmadan bu kapsamdaki değişken ...
Temel olarak kapatma, bir işleve bağımsız değişken olarak iletebileceğiniz bir kod bloğudur. C #, anonim delegeler şeklinde kapanmaları destekler.
İşte basit bir örnek:
List.Find yöntemi, listenin öğesini bulmak için kod parçasını (kapatma) kabul edebilir ve yürütebilir.
// 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; });
C # 3.0 sözdizimini kullanarak bunu şöyle yazabiliriz:
ints.Find(value => value == 1);
Kapatma, bir işlev başka bir işlevin (veya yöntemin) içinde tanımlandığında ve üst yöntemin değişkenlerini kullanmasıdır . Bir yöntemde bulunan ve içinde tanımlanan bir işleve sarılmış değişkenlerin bu kullanımına kapatma adı verilir .
Mark Seemann'ın blog yazısında oop ve fonksiyonel programlama arasında bir paralel olduğu bazı ilginç kapanış örnekleri var .
Ve daha detaylı hale getirmek için
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.
Ben de anlamaya çalışıyorum, iyi aşağıda Javascript ve C # kapatma gösteren aynı kod için kod parçacıkları vardır.
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);
}
}
Sadece mavi dışında, C # 7.0 kitabından basit ve daha anlayışlı bir cevap.
Bilmeniz gereken ön koşul : Lambda ifadesi, tanımlandığı yöntemin yerel değişkenlerine ve parametrelerine başvurabilir (dış değişkenler).
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
}
Gerçek kısım : Bir lambda ifadesi tarafından referans verilen dış değişkenlere yakalanan değişkenler denir. Değişkenleri yakalayan lambda ifadesine kapatma adı verilir.
Dikkat edilecek son nokta : Yakalanan değişkenler, temsilci gerçekten çağrıldığında değil, değişkenler yakalandığında değerlendirilir:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Satır içi anonim bir yöntem (C # 2) veya (tercihen) bir Lambda ifadesi (C # 3 +) yazarsanız, gerçek bir yöntem hala oluşturulmaktadır. Bu kod bir dış kapsam yerel değişkeni kullanıyorsa - yine de bu değişkeni bir şekilde yönteme iletmeniz gerekir.
örneğin bu Linq Where yan tümcesini (lambda ifadesini geçen basit bir uzantı yöntemidir) alın:
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;
});
Eğer bu lambda ifadesinde i kullanmak istiyorsanız, onu oluşturulan yönteme geçirmelisiniz.
Ortaya çıkan ilk soru şudur: değer veya referansla mı geçilmelidir?
Bu değişkene okuma / yazma erişimi kazandıkça referansla (sanırım) daha çok tercih edilir (ve bu C #'ın yaptığı şeydir; Microsoft'taki ekip artıları ve eksileri tartıp referansla gitti; Jon Skeet'e göre makale , Java by-value ile gitti).
Ama sonra başka bir soru ortaya çıkıyor: Bunu nereye tahsis ederim?
Aslında / doğal olarak yığına tahsis edilmeli mi? Peki, yığına tahsis edip referans olarak iletirseniz, kendi yığın çerçevesinden daha fazla olduğu durumlar olabilir. Bu örneği ele alalım:
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;
});
}
Lambda ifadesi (Where yan tümcesinde) tekrar i'yi ifade eden bir yöntem oluşturur. İ Outlive yığınına ayrılırsa, whereItems öğesini numaralandırdığınızda, oluşturulan yöntemde kullanılan i Outlive i'yi, yani yığın içinde artık erişilemeyen bir yeri gösterir.
Tamam, öyleyse yığın üzerinde ihtiyacımız var.
Yani C # derleyicisinin bu satır içi anonim / lambda'yı desteklemek için yaptığı şey, " Kapanışlar " olarak adlandırılan şeyi kullanmaktır : Öbek üzerinde i içeren bir alanı olan (ve oldukça zayıf olan ) DisplayClass adlı bir sınıf oluşturur. o.
Buna eşdeğer bir şey (ILSpy veya ILDASM kullanılarak oluşturulan IL'yi görebilirsiniz):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
Bu sınıfı yerel kapsamınızda başlatır ve i veya lambda ifadesiyle ilgili tüm kodları bu kapatma örneğiyle değiştirir. Yani - i tanımlanmış "yerel kapsam" kodunda i her kullandığınızda, aslında bu DisplayClass örnek alanı kullanıyorsunuz.
Yani ana yöntemde "local" i değiştirirseniz, aslında _DisplayClass.i değişecektir;
yani
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
"i = 10" bu dispalyclass alanına gider ve 2. numaralandırmadan hemen önce değiştirir.
Konuyla ilgili iyi bir kaynak, bu Bart De Smet Pluralsight modülüdür (kayıt gerektirir) (ayrıca "Kaldırma" terimini hatalı kullanımını da göz ardı eder - ne demek istediğimi (yani) yerel değişkenin (yani i) referans olarak değiştirildiği anlamına gelir. yeni DisplayClass alanına).
Diğer yandan, "Kapanışlar" ın döngülerle ilgili olduğu konusunda bir yanılgı var gibi görünüyor - "Kapanışlar" ın döngülerle ilgili bir kavram değil , daha ziyade anonim yöntemler / lambda ifadelerinin yerel kapsamlandırılmış değişkenlerin kullanımı olduğunu anlıyorum - bazı hileler olsa da sorular bunu göstermek için döngüler kullanır.
Kapatma, bir işlev içinde tanımlanan ve yerel değişkenlerine ve üst öğelerine erişebilen bir işlevdir.
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
böylece find yöntemi içindeki fonksiyon.
t => t.Name == name
kapsamındaki değişkenlere, t ve ebeveyn kapsamındaki değişken adına erişebilir. Bulma yöntemi tarafından bir temsilci olarak yürütülmesine rağmen, başka bir kapsamdan hep birlikte.