.NET में 'क्लोजर' क्या हैं?


195

एक बंद क्या है ? क्या उनके पास .NET में है?

यदि वे .NET में मौजूद हैं, तो क्या आप उसे समझाते हुए एक कोड स्निपेट (अधिमानतः C # में) प्रदान कर सकते हैं?

जवाबों:


258

मेरा इस विषय पर एक लेख है । (इसके बहुत सारे उदाहरण हैं।)

संक्षेप में, एक क्लोजर कोड का एक ब्लॉक है जिसे बाद के समय में निष्पादित किया जा सकता है, लेकिन जो उस वातावरण को बनाए रखता है जिसमें इसे पहली बार बनाया गया था - अर्थात यह अभी भी विधि के स्थानीय चर आदि का उपयोग कर सकता है जिसने इसे बनाया था, उसके बाद भी पद्धति ने निष्पादन को समाप्त कर दिया है।

बंद करने की सामान्य विशेषता सी # में गुमनाम तरीकों और लंबोदर भावों से लागू की गई है।

अनाम विधि का उपयोग करके एक उदाहरण दिया गया है:

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 द्वारा लौटाए गए एक्शन में अभी भी काउंटर वैरिएबल तक पहुंच है, और वास्तव में इसे बढ़ा सकते हैं, भले ही क्रिएशन खुद समाप्त हो गया हो।


57
धन्यवाद जॉन। BTW, क्या ऐसा कुछ है जो आप .NET में नहीं जानते हैं? :) जब आप सवाल करते हैं तो आप किसके पास जाते हैं?
डेवलपर

44
सीखने के लिए हमेशा अधिक होता है :) मैंने अभी तक सी # के माध्यम से सीएलआर पढ़ना बहुत ही जानकारीपूर्ण है। इसके अलावा, मैं आमतौर पर WCF / बाइंडिंग / एक्सप्रेशन ट्री के लिए मार्क ग्रेवेल और सी # भाषा की चीजों के लिए एरिक लिपर्ट से पूछता हूं।
जॉन स्कीट

2
मैंने उस पर ध्यान दिया, लेकिन मुझे अभी भी लगता है कि इसके बारे में आपका कथन "कोड का एक ब्लॉक जिसे बाद के समय में निष्पादित किया जा सकता है" बस गलत है - इसका निष्पादन से कोई लेना-देना नहीं है, चर मानों और निष्पादन से अधिक गुंजाइश है। , दर असल।
जेसन बंटिंग

11
मैं कहूंगा कि जब तक उन्हें निष्पादित नहीं किया जा सकता है, क्लोजर उपयोगी नहीं हैं, और "बाद के समय में" पर्यावरण को पकड़ने में सक्षम होने की "विषमता" को उजागर करता है (जो अन्यथा निष्पादन समय से दूर हो गया हो सकता है)। यदि आप केवल आधा वाक्य उद्धृत करते हैं तो यह एक अपूर्ण उत्तर है, निश्चित रूप से।
जॉन स्कीट

4
@ एसएलसी: हां, counterवेतन वृद्धि के लिए उपलब्ध है - संकलक एक वर्ग उत्पन्न करता है जिसमें एक counterक्षेत्र होता है , और किसी भी कोड का counterअंत उस वर्ग के एक उदाहरण के माध्यम से जाना होता है।
जॉन स्कीट

22

यदि आप यह देखने में रुचि रखते हैं कि 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);
    }
}

हाय, डेनियल - आप उत्तर बहुत उपयोगी हैं और मैं आपके उत्तर से आगे बढ़कर अनुसरण करना चाहता था लेकिन लिंक टूट गया है। दुर्भाग्य से, मेरा googlefu यह खोजने के लिए पर्याप्त नहीं है कि यह कहां गया।
नॉक्स

10

क्लोज़र्स कार्यात्मक मान हैं जो अपने मूल दायरे से परिवर्तनशील मूल्यों पर पकड़ रखते हैं। अनाम प्रतिनिधियों के रूप में C # का उपयोग कर सकते हैं।

एक बहुत ही सरल उदाहरण के लिए, यह C # कोड लें:

    delegate int testDel();

    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();

    }

इसके अंत में, बार को 4 पर सेट किया जाएगा, और myClosure प्रतिनिधि को कार्यक्रम में कहीं और इस्तेमाल करने के लिए पास किया जा सकता है।

क्लोजर का उपयोग बहुत सारी उपयोगी चीजों के लिए किया जा सकता है, जैसे विलंबित निष्पादन या इंटरफेस को सरल बनाने के लिए - LINQ मुख्य रूप से क्लोजर का उपयोग करके बनाया गया है। अधिकांश डेवलपर्स के लिए सबसे तात्कालिक तरीका यह काम करता है कि गतिशील रूप से बनाए गए नियंत्रणों में ईवेंट हैंडलर जोड़ रहा है - आप डेटा को कहीं और संग्रहीत करने के बजाय नियंत्रण को तत्काल जोड़ने पर व्यवहार को जोड़ने के लिए क्लोजर का उपयोग कर सकते हैं।


10
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

एक क्लोजर एक अनाम फ़ंक्शन है जिसे उस फ़ंक्शन के बाहर पारित किया जाता है जिसमें यह बनाया गया है। यह उस फ़ंक्शन से किसी भी चर को बनाए रखता है जिसमें यह बनाया जाता है कि यह उपयोग करता है।


4

यहाँ 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

आशा है कि कुछ हद तक मददगार है।


1
आपने एक उदाहरण दिया है, लेकिन एक सामान्य परिभाषा प्रस्तुत नहीं की है। मैं आपकी टिप्पणियों से यहाँ इकट्ठा करता हूं कि वे 'गुंजाइश के बारे में अधिक हैं', लेकिन निश्चित रूप से इससे कहीं अधिक है?
लड्डू

2

मूल रूप से बंद करना कोड का एक ब्लॉक है जिसे आप एक फ़ंक्शन के तर्क के रूप में पास कर सकते हैं। अनाम प्रतिनिधियों के रूप में 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);

1
मुझे तकनीकी होने से नफरत है, लेकिन बंद करने का दायरा अधिक है - एक बंद को अलग-अलग तरीकों से बनाया जा सकता है, लेकिन एक करीबी साधन नहीं है, यह अंत है।
जेसन बंटिंग

2

एक समापन तब होता है जब एक फ़ंक्शन को किसी अन्य फ़ंक्शन (या विधि) के अंदर परिभाषित किया जाता है और यह मूल विधि से चर का उपयोग करता है । चर का यह उपयोग जो एक विधि में स्थित है और इसके भीतर परिभाषित फ़ंक्शन में लिपटे हैं, इसे एक बंद कहा जाता है।

मार्क सीमैन ने अपने ब्लॉग पोस्ट में बंद होने के कुछ दिलचस्प उदाहरण दिए हैं जहां वह ऊप और कार्यात्मक प्रोग्रामिंग के बीच एक परवल करता है।

और इसे और विस्तृत बनाने के लिए

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.

1

क्लोजर कोड के खंड हैं जो स्वयं के बाहर एक चर का संदर्भ देते हैं, (स्टैक पर उनके नीचे से), जिसे बाद में कॉल किया जा सकता है या निष्पादित किया जा सकता है (जैसे कि जब कोई घटना या प्रतिनिधि परिभाषित किया जाता है, और समय पर कुछ अनिश्चित भविष्य के बिंदु पर कॉल किया जा सकता है) ) ... क्योंकि बाहरी चर जो कोड संदर्भों का हिस्सा दायरे से बाहर हो सकता है (और अन्यथा खो जाएगा), तथ्य यह है कि यह कोड के चंक द्वारा संदर्भित है (एक करीबी कहा जाता है) रनटाइम को "पकड़" बताता है "उस दायरे को उस दायरे तक में जब तक कोड के बंद होने की जरूरत नहीं है ...


जैसा कि मैंने किसी और के स्पष्टीकरण पर संकेत दिया है: मैं तकनीकी होने से नफरत करता हूं, लेकिन बंद करने का दायरा अधिक है - एक करीबी को अलग-अलग तरीकों से बनाया जा सकता है, लेकिन एक करीबी साधन नहीं है, यह अंत है।
जेसन बंटिंग

1
क्लोजर मेरे लिए अपेक्षाकृत नया है, इसलिए यह पूरी तरह से संभव है कि मुझे गलतफहमी हो, लेकिन मुझे गुंजाइश मिलती है .. मेरा जवाब गुंजाइश के आसपास केंद्रित है। इसलिए मुझे याद आ रहा है कि क्या yr कमेंट सही करने की कोशिश कर रहा है ... और क्या हो सकता है लेकिन कुछ कोड के लिए प्रासंगिक हो सकता है? (समारोह, अनाम विधि, या जो भी)
चार्ल्स ब्रेटाना

एक बंद करने की कुंजी नहीं है कि कुछ "रन करने योग्य कोड का हिस्सा" एक चर या इन-मेमोरी मान तक पहुंच सकता है जो इसे "गुंजाइश" के बाहर "निष्क्रिय" करता है, उसके बाद चर को सामान्य रूप से "दायरे से बाहर" जाना चाहिए या नष्ट हो जाना चाहिए। ?
चार्ल्स ब्रेटाना

और @ जैसन, तकनीकी होने के बारे में कोई चिंता नहीं है, यह बंद विचार कुछ ऐसा है जिसे मैंने अपने सिर को लपेटने के लिए थोड़ी देर ली, एक सहकर्मी के साथ लंबी चर्चा में, जावास्क्रिप्ट बंद होने के बारे में ... लेकिन वह एक लिस्प नट था और मैंने कभी नहीं उनके स्पष्टीकरण में सार के माध्यम से मिला ...
चार्ल्स Bretana

0

मैं इसे भी समझने की कोशिश कर रहा हूं, अच्छी तरह से नीचे जावास्क्रिप्ट में समान कोड के लिए कोड स्निपेट हैं और सी # बंद दिखा रहा है।

  1. प्रत्येक घटना की गणना संख्या की होती है या प्रत्येक बटन पर क्लिक करने की कोई संख्या होती है।

जावास्क्रिप्ट:

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);
    }
}
  1. गणना कुल बार क्लिक की घटना हुई है या नियंत्रण की परवाह किए बिना क्लिक की कुल संख्या की गणना करें।

जावास्क्रिप्ट:

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

0

नीले रंग से बाहर, पुस्तक सी # 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

0

यदि आप एक इनलाइन अनाम विधि (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 फ़ील्ड के लिए)।


अन्य समाचारों में, कुछ गलत धारणा है कि "क्लोज़र" लूप से संबंधित हैं - जैसा कि मैं समझता हूं कि "क्लोज़र" लूप से संबंधित अवधारणा नहीं है , बल्कि स्थानीय स्कोप चर के अनाम तरीकों / लंबो एक्सप्रेशंस का उपयोग करते हैं - हालांकि कुछ ट्रिक प्रश्न इसे प्रदर्शित करने के लिए लूप का उपयोग करते हैं।


-1

एक क्लोजर एक फ़ंक्शन है, जिसे एक फ़ंक्शन के भीतर परिभाषित किया गया है, जो इसके स्थानीय चर के साथ-साथ इसके माता-पिता तक भी पहुंच सकता है।

public string GetByName(string name)
{
   List<things> theThings = new List<things>();
  return  theThings.Find<things>(t => t.Name == name)[0];
}

इसलिए खोज विधि के अंदर कार्य करते हैं।

 t => t.Name == name

चर के दायरे में प्रवेश कर सकते हैं, टी, और चर नाम जो इसके माता-पिता के दायरे में है। भले ही यह एक प्रतिनिधि के रूप में खोज विधि द्वारा निष्पादित किया जाता है, एक और गुंजाइश से सभी एक साथ।


2
एक बंद एक कार्य नहीं है, प्रति से, यह कार्यों की तुलना में गुंजाइश के बारे में बात करके अधिक परिभाषित किया गया है। कार्य केवल गुंजाइश को बनाए रखने में सहायता करते हैं, जो एक बंद होने का कारण बनता है। लेकिन यह कहना कि एक समारोह तकनीकी रूप से सही नहीं है। निपिक के लिए क्षमा करें। :)
जेसन बंटिंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.