जवाबों:
मेरा इस विषय पर एक लेख है । (इसके बहुत सारे उदाहरण हैं।)
संक्षेप में, एक क्लोजर कोड का एक ब्लॉक है जिसे बाद के समय में निष्पादित किया जा सकता है, लेकिन जो उस वातावरण को बनाए रखता है जिसमें इसे पहली बार बनाया गया था - अर्थात यह अभी भी विधि के स्थानीय चर आदि का उपयोग कर सकता है जिसने इसे बनाया था, उसके बाद भी पद्धति ने निष्पादन को समाप्त कर दिया है।
बंद करने की सामान्य विशेषता सी # में गुमनाम तरीकों और लंबोदर भावों से लागू की गई है।
अनाम विधि का उपयोग करके एक उदाहरण दिया गया है:
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);
};
}
}
आउटपुट:
counter=1
counter=2
यहां हम देख सकते हैं कि CreateAction द्वारा लौटाए गए एक्शन में अभी भी काउंटर वैरिएबल तक पहुंच है, और वास्तव में इसे बढ़ा सकते हैं, भले ही क्रिएशन खुद समाप्त हो गया हो।
counter
वेतन वृद्धि के लिए उपलब्ध है - संकलक एक वर्ग उत्पन्न करता है जिसमें एक counter
क्षेत्र होता है , और किसी भी कोड का counter
अंत उस वर्ग के एक उदाहरण के माध्यम से जाना होता है।
यदि आप यह देखने में रुचि रखते हैं कि C # लागू कैसे बंद होता है "मुझे इसका उत्तर पता है (इसका 42) ब्लॉग"
कंपाइलर पृष्ठभूमि में एक वर्ग उत्पन्न करता है जो एनोयमस विधि और चर जे को अलग करता है
[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
public <>c__DisplayClass2();
public void <fillFunc>b__0()
{
Console.Write("{0} ", this.j);
}
public int j;
}
समारोह के लिए:
static void fillFunc(int count) {
for (int i = 0; i < count; i++)
{
int j = i;
funcArr[i] = delegate()
{
Console.Write("{0} ", j);
};
}
}
इसे चालू करना:
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);
}
}
क्लोज़र्स कार्यात्मक मान हैं जो अपने मूल दायरे से परिवर्तनशील मूल्यों पर पकड़ रखते हैं। अनाम प्रतिनिधियों के रूप में C # का उपयोग कर सकते हैं।
एक बहुत ही सरल उदाहरण के लिए, यह C # कोड लें:
delegate int testDel();
static void Main(string[] args)
{
int foo = 4;
testDel myClosure = delegate()
{
return foo;
};
int bar = myClosure();
}
इसके अंत में, बार को 4 पर सेट किया जाएगा, और myClosure प्रतिनिधि को कार्यक्रम में कहीं और इस्तेमाल करने के लिए पास किया जा सकता है।
क्लोजर का उपयोग बहुत सारी उपयोगी चीजों के लिए किया जा सकता है, जैसे विलंबित निष्पादन या इंटरफेस को सरल बनाने के लिए - LINQ मुख्य रूप से क्लोजर का उपयोग करके बनाया गया है। अधिकांश डेवलपर्स के लिए सबसे तात्कालिक तरीका यह काम करता है कि गतिशील रूप से बनाए गए नियंत्रणों में ईवेंट हैंडलर जोड़ रहा है - आप डेटा को कहीं और संग्रहीत करने के बजाय नियंत्रण को तत्काल जोड़ने पर व्यवहार को जोड़ने के लिए क्लोजर का उपयोग कर सकते हैं।
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
एक क्लोजर एक अनाम फ़ंक्शन है जिसे उस फ़ंक्शन के बाहर पारित किया जाता है जिसमें यह बनाया गया है। यह उस फ़ंक्शन से किसी भी चर को बनाए रखता है जिसमें यह बनाया जाता है कि यह उपयोग करता है।
यहाँ C # के लिए एक आकस्मिक उदाहरण है जो मैंने जावास्क्रिप्ट में समान कोड से बनाया है:
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; };
}
तो, यहाँ कुछ कोड है जो दिखाता है कि उपरोक्त कोड का उपयोग कैसे करें ...
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
आशा है कि कुछ हद तक मददगार है।
मूल रूप से बंद करना कोड का एक ब्लॉक है जिसे आप एक फ़ंक्शन के तर्क के रूप में पास कर सकते हैं। अनाम प्रतिनिधियों के रूप में C # बंद का समर्थन करता है।
यहाँ एक सरल उदाहरण है:
List.Find विधि सूची के आइटम को खोजने के लिए कोड (बंद) के टुकड़े को स्वीकार और निष्पादित कर सकती है।
// 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 सिंटैक्स का उपयोग करके हम इसे इस प्रकार लिख सकते हैं:
ints.Find(value => value == 1);
एक समापन तब होता है जब एक फ़ंक्शन को किसी अन्य फ़ंक्शन (या विधि) के अंदर परिभाषित किया जाता है और यह मूल विधि से चर का उपयोग करता है । चर का यह उपयोग जो एक विधि में स्थित है और इसके भीतर परिभाषित फ़ंक्शन में लिपटे हैं, इसे एक बंद कहा जाता है।
मार्क सीमैन ने अपने ब्लॉग पोस्ट में बंद होने के कुछ दिलचस्प उदाहरण दिए हैं जहां वह ऊप और कार्यात्मक प्रोग्रामिंग के बीच एक परवल करता है।
और इसे और विस्तृत बनाने के लिए
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.
क्लोजर कोड के खंड हैं जो स्वयं के बाहर एक चर का संदर्भ देते हैं, (स्टैक पर उनके नीचे से), जिसे बाद में कॉल किया जा सकता है या निष्पादित किया जा सकता है (जैसे कि जब कोई घटना या प्रतिनिधि परिभाषित किया जाता है, और समय पर कुछ अनिश्चित भविष्य के बिंदु पर कॉल किया जा सकता है) ) ... क्योंकि बाहरी चर जो कोड संदर्भों का हिस्सा दायरे से बाहर हो सकता है (और अन्यथा खो जाएगा), तथ्य यह है कि यह कोड के चंक द्वारा संदर्भित है (एक करीबी कहा जाता है) रनटाइम को "पकड़" बताता है "उस दायरे को उस दायरे तक में जब तक कोड के बंद होने की जरूरत नहीं है ...
मैं इसे भी समझने की कोशिश कर रहा हूं, अच्छी तरह से नीचे जावास्क्रिप्ट में समान कोड के लिए कोड स्निपेट हैं और सी # बंद दिखा रहा है।
जावास्क्रिप्ट:
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>
सी#:
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);
}
}
जावास्क्रिप्ट:
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()"/>
सी#:
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);
}
}
नीले रंग से बाहर, पुस्तक सी # 7.0 संक्षेप से एक सरल और अधिक समझ वाला उत्तर।
पूर्व-अपेक्षित आपको पता होना चाहिए : एक लंबोदर अभिव्यक्ति स्थानीय चर और उस पद्धति के मापदंडों को संदर्भित कर सकती है जिसमें इसे परिभाषित किया गया है (बाहरी चर)।
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
}
असली हिस्सा : एक लैम्ब्डा अभिव्यक्ति द्वारा संदर्भित बाहरी चरों को कैप्चर किए गए चर कहा जाता है। एक लैम्ब्डा अभिव्यक्ति जो चर को पकड़ती है उसे क्लोजर कहा जाता है।
अंतिम बिंदु ध्यान दिया जाना चाहिए : कैप्चर किए गए चर का मूल्यांकन तब किया जाता है जब प्रतिनिधि वास्तव में आह्वान किया जाता है, न कि तब जब चर पकड़े गए थे:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
यदि आप एक इनलाइन अनाम विधि (C # 2) या (अधिमानतः) एक लैम्ब्डा अभिव्यक्ति (C # 3 +) लिखते हैं, तो एक वास्तविक विधि अभी भी बनाई जा रही है। यदि वह कोड किसी बाहरी-स्कोप के स्थानीय वैरिएबल का उपयोग कर रहा है - आपको अभी भी उस वैरिएबल को किसी तरीके से पास करना होगा।
उदाहरण के लिए इस लाइन को ले जाएँ जहाँ क्लॉज़ (जो एक साधारण एक्सटेंशन विधि है जो लैम्बडा एक्सप्रेशन पास करती है):
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;
});
यदि आप उस लंबोदर अभिव्यक्ति में मैं का उपयोग करना चाहते हैं, तो आपको इसे उस बनाई गई विधि से पारित करना होगा।
तो पहला सवाल जो उठता है वह है: क्या इसे मूल्य या संदर्भ द्वारा पारित किया जाना चाहिए?
संदर्भ द्वारा पास (मुझे लगता है) अधिक बेहतर है क्योंकि आप उस चर तक पहुंच / पढ़ते हैं (और यह वही है जो C # करता है; मुझे लगता है कि Microsoft में टीम ने पेशेवरों और विपक्षों का वजन किया और उप-संदर्भ के साथ चला गया; जॉन स्कीट के अनुसार; लेख , जावा बाय-वैल्यू के साथ गया)।
लेकिन फिर एक और सवाल उठता है: मुझे कहाँ आवंटित करना है?
क्या इसे वास्तव में / स्वाभाविक रूप से स्टैक पर आवंटित किया जाना चाहिए? ठीक है, यदि आप इसे स्टैक पर आवंटित करते हैं और इसे संदर्भ द्वारा पास करते हैं, तो ऐसी परिस्थितियां हो सकती हैं, जहां यह रूपरेखा है कि यह स्वयं स्टैक फ्रेम है। इस उदाहरण को लें:
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;
});
}
लैम्ब्डा एक्सप्रेशन (जहां क्लॉज में) फिर से एक विधि बनाता है जो आई को संदर्भित करता है। यदि मुझे आउटलाइव के स्टैक पर आवंटित किया जाता है, तो जब तक आप इसमें आयेंगे, तब तक उत्पन्न विधि में प्रयुक्त i आउटलेव के i को इंगित करेगा, यानी स्टैक में एक जगह पर जो अब सुलभ नहीं है।
ठीक है, तो हम इसे ढेर पर फिर जरूरत है।
तो सी # संकलक इस इनलाइन अनाम / लैम्ब्डा का समर्थन करने के लिए क्या करता है, इसका उपयोग " क्लोज़र " कहा जाता है : यह हीप पर एक वर्ग बनाता है जिसे बुलाया जाता है ( बल्कि खराब ) डिस्प्लेक्लास जिसमें एक फ़ील्ड होता है जिसमें i होता है, और फ़ंक्शन जो वास्तव में उपयोग होता है यह।
ऐसा कुछ जो इसके बराबर होगा (आप ILSpy या ILDASM का उपयोग करके उत्पन्न IL देख सकते हैं):
class <>c_DisplayClass1
{
public int i;
public bool <GetFunc>b__0()
{
this.i++;
Console.WriteLine(i);
return true;
}
}
यह आपके स्थानीय दायरे में उस वर्ग को तत्काल बदल देता है, और उस बंद उदाहरण के साथ i या लैम्ब्डा अभिव्यक्ति से संबंधित किसी भी कोड को बदल देता है। इसलिए - कभी भी आप अपने "स्थानीय दायरे" कोड में i का उपयोग कर रहे हैं जहां मैं परिभाषित किया गया था, आप वास्तव में उस DisplayClass उदाहरण क्षेत्र का उपयोग कर रहे हैं।
इसलिए अगर मैं "स्थानीय" i को मुख्य विधि में बदल दूंगा, तो यह वास्तव में _DisplayClass.i बदल जाएगा;
अर्थात
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
यह 12 को प्रिंट करेगा, जैसा कि "i = 10" उस डिस्पैलेक्लास क्षेत्र में जाता है और इसे दूसरी गणना से ठीक पहले बदल देता है।
विषय पर एक अच्छा स्रोत यह बार डी स्मेट प्लुरललाइट मॉड्यूल है (पंजीकरण की आवश्यकता है) ("उत्थापन" शब्द के उनके गलत उपयोग को भी अनदेखा करें - इसका क्या मतलब है) मुझे लगता है कि स्थानीय चर (यानी i) को संदर्भित करने के लिए बदल दिया गया है नई DisplayClass फ़ील्ड के लिए)।
अन्य समाचारों में, कुछ गलत धारणा है कि "क्लोज़र" लूप से संबंधित हैं - जैसा कि मैं समझता हूं कि "क्लोज़र" लूप से संबंधित अवधारणा नहीं है , बल्कि स्थानीय स्कोप चर के अनाम तरीकों / लंबो एक्सप्रेशंस का उपयोग करते हैं - हालांकि कुछ ट्रिक प्रश्न इसे प्रदर्शित करने के लिए लूप का उपयोग करते हैं।
एक क्लोजर एक फ़ंक्शन है, जिसे एक फ़ंक्शन के भीतर परिभाषित किया गया है, जो इसके स्थानीय चर के साथ-साथ इसके माता-पिता तक भी पहुंच सकता है।
public string GetByName(string name)
{
List<things> theThings = new List<things>();
return theThings.Find<things>(t => t.Name == name)[0];
}
इसलिए खोज विधि के अंदर कार्य करते हैं।
t => t.Name == name
चर के दायरे में प्रवेश कर सकते हैं, टी, और चर नाम जो इसके माता-पिता के दायरे में है। भले ही यह एक प्रतिनिधि के रूप में खोज विधि द्वारा निष्पादित किया जाता है, एक और गुंजाइश से सभी एक साथ।