ReSharper ने मुझे "स्पष्ट रूप से कब्जा कर लिया बंद" क्यों बताया?


296

मेरे पास निम्नलिखित कोड हैं:

public double CalculateDailyProjectPullForceMax(DateTime date, string start = null, string end = null)
{
    Log("Calculating Daily Pull Force Max...");

    var pullForceList = start == null
                             ? _pullForce.Where((t, i) => _date[i] == date).ToList() // implicitly captured closure: end, start
                             : _pullForce.Where(
                                 (t, i) => _date[i] == date && DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 && 
                                           DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList();

    _pullForceDailyMax = Math.Round(pullForceList.Max(), 2, MidpointRounding.AwayFromZero);

    return _pullForceDailyMax;
}

अब, मैंने उस लाइन पर एक टिप्पणी जोड़ी है जिसे ReSharper एक बदलाव का सुझाव दे रहा है। इसका क्या मतलब है, या इसे बदलने की आवश्यकता क्यों होगी?implicitly captured closure: end, start


6
MyCodeSucks कृपया स्वीकार किए गए उत्तर को ठीक करें: केविंगेसनर का एक गलत है (जैसा कि टिप्पणियों में बताया गया है) और इसे स्वीकार किए जाने के रूप में चिह्नित किया गया है यदि वे कंसोल के उत्तर को नोटिस नहीं करते हैं तो वे उपयोगकर्ताओं को भ्रमित करेंगे।
अल्बेरियो

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

जवाबों:


391

चेतावनी आपको बताती है कि चर endऔर startजीवित रहें क्योंकि इस पद्धति के अंदर कोई भी लंबोदा जीवित रहता है।

छोटे उदाहरण पर एक नज़र डालें

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    int i = 0;
    Random g = new Random();
    this.button1.Click += (sender, args) => this.label1.Text = i++.ToString();
    this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString();
}

मुझे पहले लैम्ब्डा में "इम्प्लांटली कैप्चर क्लोजर: जी" चेतावनी मिलती है। यह मुझे बता रहा है कि कचरा एकत्रg नहीं किया जा सकता है जब तक पहला मेमना उपयोग में ।

कंपाइलर लैम्बडा एक्सप्रेशंस के लिए एक क्लास बनाता है और उस क्लास में सभी वेरिएबल्स डालता है जो लैम्बडा एक्सप्रेशन्स में उपयोग किए जाते हैं।

इसलिए मेरे उदाहरण में gऔर iमेरे प्रतिनिधियों के निष्पादन के लिए एक ही कक्षा में आयोजित किए जाते हैं। यदि gभारी संसाधनों को पीछे छोड़ दिया गया है, तो कचरा संग्रहकर्ता इसे पुनः प्राप्त नहीं कर सकता है, क्योंकि इस वर्ग में संदर्भ तब तक जीवित है, जब तक कि लैम्ब्डा अभिव्यक्ति का कोई उपयोग नहीं होता है। तो यह एक संभावित मेमोरी लीक है, और यही R # चेतावनी का कारण है।

@splintor C # में अनाम विधियों को हमेशा एक कक्षा प्रति विधि में संग्रहीत किया जाता है, इससे बचने के दो तरीके हैं:

  1. अनाम के बजाय एक इंस्टेंस विधि का उपयोग करें।

  2. लंबोदर भाव की रचना को दो विधियों में विभाजित करें।


30
इस कैप्चर से बचने के क्या संभव तरीके हैं?
बंटवारा

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

1
@ एसएसपी प्रतिनिधि के अंदर की वस्तु को तुरंत निकालता है, या इसके बजाय एक पैरामीटर के रूप में पास करता है। उपरोक्त मामले में, जहां तक ​​मैं बता सकता हूं, वांछित व्यवहार वास्तव में Randomउदाहरण के लिए एक संदर्भ रखना है , हालांकि।
केसी

2
@emodendroket सही, इस बिंदु पर हम कोड शैली और पठनीयता पर बात कर रहे हैं। एक क्षेत्र के बारे में तर्क करना आसान है। यदि मेमोरी प्रेशर या ऑब्जेक्ट लाइफटाइम महत्वपूर्ण हैं, तो मैंने फ़ील्ड को चुना है, अन्यथा मैं इसे और अधिक संक्षिप्त समापन में छोड़ देता।
यजोर

1
मेरा मामला (भारी) सरलीकृत उबला हुआ एक फैक्ट्री विधि है जो फू और बार बनाता है। यह उन दो वस्तुओं द्वारा उजागर की गई घटनाओं के लिए लैंबों को कैप्चर करने की सदस्यता लेता है और, आश्चर्यचकित करता है, फू बार घटना के लांबा से कैप्चर को जीवित रखता है और इसके विपरीत। मैं C ++ से आता हूं, जहां इस दृष्टिकोण ने ठीक काम किया होगा, और यहां नियमों को खोजने के लिए थोड़ा चकित होने की तुलना में अधिक आश्चर्यजनक था। जितना आप जानते हैं, मुझे लगता है।
डैल

35

पीटर मोर्टेंसन से सहमत।

C # संकलक केवल एक प्रकार उत्पन्न करता है जो एक विधि में सभी लंबोदर अभिव्यक्तियों के लिए सभी चर को अलग करता है।

उदाहरण के लिए, स्रोत कोड दिया गया:

public class ValueStore
{
    public Object GetValue()
    {
        return 1;
    }

    public void SetValue(Object obj)
    {
    }
}

public class ImplicitCaptureClosure
{
    public void Captured()
    {
        var x = new object();

        ValueStore store = new ValueStore();
        Action action = () => store.SetValue(x);
        Func<Object> f = () => store.GetValue();    //Implicitly capture closure: x
    }
}

कंपाइलर एक प्रकार बनाता है जैसा दिखता है:

[CompilerGenerated]
private sealed class c__DisplayClass2
{
  public object x;
  public ValueStore store;

  public c__DisplayClass2()
  {
    base.ctor();
  }

  //Represents the first lambda expression: () => store.SetValue(x)
  public void Capturedb__0()
  {
    this.store.SetValue(this.x);
  }

  //Represents the second lambda expression: () => store.GetValue()
  public object Capturedb__1()
  {
    return this.store.GetValue();
  }
}

और Captureविधि के रूप में संकलित है:

public void Captured()
{
  ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2();
  cDisplayClass2.x = new object();
  cDisplayClass2.store = new ValueStore();
  Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0));
  Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1));
}

हालाँकि, दूसरा लैंबडा उपयोग नहीं करता है x, इसे एकत्र नहीं किया जा सकता xहै क्योंकि लैम्बडा में प्रयुक्त उत्पन्न वर्ग की संपत्ति के रूप में संकलित है।


31

चेतावनी मान्य है और उन विधियों में प्रदर्शित होती है जिनमें एक से अधिक लैंबडा होते हैं , और वे विभिन्न मूल्यों को कैप्चर करते हैं

जब एक विधि जिसमें लंबोदर शामिल होता है, एक कंपाइलर-जेनरेट की गई वस्तु के साथ त्वरित किया जाता है:

  • उदाहरण के तरीके लैम्ब्डा का प्रतिनिधित्व करते हैं
  • सभी मूल्यों का प्रतिनिधित्व क्षेत्रों द्वारा कब्जा कर लिया किसी भी उन lambdas की

उदहारण के लिए:

class DecompileMe
{
    DecompileMe(Action<Action> callable1, Action<Action> callable2)
    {
        var p1 = 1;
        var p2 = "hello";

        callable1(() => p1++);    // WARNING: Implicitly captured closure: p2

        callable2(() => { p2.ToString(); p1++; });
    }
}

इस वर्ग के लिए उत्पन्न कोड की जाँच करें (थोड़ा सा ऊपर):

class DecompileMe
{
    DecompileMe(Action<Action> callable1, Action<Action> callable2)
    {
        var helper = new LambdaHelper();

        helper.p1 = 1;
        helper.p2 = "hello";

        callable1(helper.Lambda1);
        callable2(helper.Lambda2);
    }

    [CompilerGenerated]
    private sealed class LambdaHelper
    {
        public int p1;
        public string p2;

        public void Lambda1() { ++p1; }

        public void Lambda2() { p2.ToString(); ++p1; }
    }
}

LambdaHelperनिर्मित दुकानों के उदाहरण पर ध्यान दें p1औरp2

कल्पना करो कि:

  • callable1 अपने तर्क का लंबे समय तक संदर्भ रखता है, helper.Lambda1
  • callable2 अपने तर्क का संदर्भ नहीं रखता है, helper.Lambda2

इस स्थिति में, helper.Lambda1अप्रत्यक्ष रूप से स्ट्रिंग में संदर्भ भी संदर्भित करता है p2और इसका मतलब यह है कि कचरा संग्रहकर्ता इससे निपटने में सक्षम नहीं होगा। कम से कम यह एक मेमोरी / रिसोर्स लीक है। वैकल्पिक रूप से यह ऑब्जेक्ट (नों) को जरूरत से ज्यादा समय तक जीवित रख सकता है, जो जीसी पर प्रभाव डाल सकता है यदि वे जीन0 से जीन 1 तक पदोन्नत होते हैं।


अगर हम इस तरह p1से संदर्भ निकालते हैं callable2: callable2(() => { p2.ToString(); });- तो क्या यह अभी भी उसी मुद्दे का कारण नहीं बनेगा (कचरा संग्रहकर्ता इससे निपटने में सक्षम नहीं होगा) जैसा LambdaHelperकि अभी भी होगा p1और p2?
एंटनी

1
हां, वही समस्या मौजूद होगी। कंपाइलर LambdaHelperमूल विधि के भीतर सभी लैम्ब्डा के लिए एक कैप्चर ऑब्जेक्ट (यानी ऊपर) बनाता है । तो भी अगर callable2कोई फायदा नहीं हुआ p1, तो यह उसी कैप्चर ऑब्जेक्ट को शेयर करेगा callable1, और उस कैप्चर ऑब्जेक्ट को रेफर करेगा p1और p2। ध्यान दें कि यह केवल संदर्भ प्रकारों के लिए ही मायने रखता है, और p1इस उदाहरण में एक मूल्य प्रकार है।
ड्रू नोक

3

Linq से Sql प्रश्नों के लिए, आपको यह चेतावनी मिल सकती है। लैम्बडा का दायरा इस तथ्य के कारण विधि को रेखांकित कर सकता है कि विधि के दायरे से बाहर होने के बाद क्वेरी को अक्सर वास्तविक रूप दिया जाता है। अपनी स्थिति के आधार पर, आप L2S लैम्ब्डा में कैप्चर की गई विधि के उदाहरण var पर GC की अनुमति देने के लिए विधि के भीतर परिणाम (यानी .ToList () के माध्यम से) को वास्तविक रूप देना चाह सकते हैं।


2

आप हमेशा नीचे दिखाए गए संकेतों की तरह क्लिक करके R # सुझावों के कारणों का पता लगा सकते हैं:

यहां छवि विवरण दर्ज करें

यह संकेत आपको यहां निर्देशित करेगा ।


यह निरीक्षण इस तथ्य की ओर आपका ध्यान आकर्षित करता है कि स्पष्ट रूप से देखने की तुलना में अधिक करीबी मूल्यों पर कब्जा किया जा रहा है, जिसका इन मूल्यों के जीवनकाल पर प्रभाव पड़ता है।

निम्नलिखित कोड पर विचार करें:

using System; 
public class Class1 {
    private Action _someAction;

    public void Method() {
        var obj1 = new object();
        var obj2 = new object();

        _someAction += () => {
            Console.WriteLine(obj1);
            Console.WriteLine(obj2);
        };

        // "Implicitly captured closure: obj2"
        _someAction += () => {
            Console.WriteLine(obj1);
        };
    }
}

पहले क्लोजर में, हम देखते हैं कि obj1 और obj2 दोनों को स्पष्ट रूप से कैप्चर किया जा रहा है; हम इसे केवल कोड को देखकर देख सकते हैं। दूसरे बंद के लिए, हम देख सकते हैं कि obj1 को स्पष्ट रूप से कैप्चर किया जा रहा है, लेकिन ReSharper हमें चेतावनी दे रहा है कि obj2 को अंतर्निहित रूप से कैप्चर किया जा रहा है।

यह सी # कंपाइलर में कार्यान्वयन विस्तार के कारण है। संकलन के दौरान, क्लोज़र उन फ़ील्ड्स के साथ वर्गों में फिर से लिखे जाते हैं जो कैप्चर किए गए मानों को रखते हैं, और वे विधियाँ जो क्लोजर को दर्शाती हैं। C # कंपाइलर केवल एक ऐसी निजी क्लास प्रति मेथड बनाएगा, और यदि एक से अधिक क्लोजर को एक विधि में परिभाषित किया जाता है, तो इस क्लास में कई तरीके होंगे, प्रत्येक क्लोजर के लिए एक, और इसमें सभी क्लोजर से सभी कैप्चर किए गए मान भी शामिल होंगे।

यदि हम उस कोड को देखते हैं जो संकलक उत्पन्न करता है, तो यह थोड़ा सा दिखता है (कुछ नामों को पढ़ने में आसानी के लिए साफ किया गया है):

public class Class1 {
    [CompilerGenerated]
    private sealed class <>c__DisplayClass1_0
    {
        public object obj1;
        public object obj2;

        internal void <Method>b__0()
        {
            Console.WriteLine(obj1);
            Console.WriteLine(obj2);
        }

        internal void <Method>b__1()
        {
            Console.WriteLine(obj1);
        }
    }

    private Action _someAction;

    public void Method()
    {
        // Create the display class - just one class for both closures
        var dc = new Class1.<>c__DisplayClass1_0();

        // Capture the closure values as fields on the display class
        dc.obj1 = new object();
        dc.obj2 = new object();

        // Add the display class methods as closure values
        _someAction += new Action(dc.<Method>b__0);
        _someAction += new Action(dc.<Method>b__1);
    }
}

जब विधि चलती है, तो यह डिस्प्ले क्लास बनाता है, जो सभी मूल्यों को कैप्चर करता है, सभी क्लोजर के लिए। यहां तक ​​कि अगर किसी एक क्लोजर में मूल्य का उपयोग नहीं किया जाता है, तो भी इसे कैप्चर किया जाएगा। यह "निहित" कैप्चर है जिसे ReSharper हाइलाइट कर रहा है।

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.