কেন রিশার্পার আমাকে "অন্তর্নিহিতভাবে বন্দী হওয়া" বলছেন?


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

এখন, আমি রেখায় একটি মন্তব্য যুক্ত করেছি যে রিশার্পার একটি পরিবর্তন প্রস্তাব করছে। এর অর্থ কী, বা কেন এটি পরিবর্তন করা দরকার?implicitly captured closure: end, start


6
মাইকোডসাক্স দয়া করে গৃহীত উত্তরটি ঠিক করুন: কেভিনজেসনারের একটিটি ভুল (মন্তব্যগুলিতে ব্যাখ্যা করা হয়েছে) এবং এটি স্বীকৃত হিসাবে চিহ্নিত করা থাকলে তারা কনসোলের উত্তর লক্ষ্য না করলে ব্যবহারকারীদের বিভ্রান্ত করবে।
আলবিরিও

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প্রচুর সংস্থান রেখে কোনও ভারী অবজেক্ট থাকে তবে আবর্জনা সংগ্রহকারী এটি পুনরায় দাবি করতে পারে না, কারণ এই শ্রেণীর রেফারেন্স এখনও যতক্ষণ বেঁচে আছে যতক্ষণ ল্যাম্বডা এর কোনও মত প্রকাশ ব্যবহৃত হয়। সুতরাং এটি একটি সম্ভাব্য মেমরি ফাঁস, এবং এটি আর # সতর্কতার কারণ।

@ স্প্লিন্টর সি # এর মতো বেনামে পদ্ধতিগুলি সর্বদা পদ্ধতি অনুসারে একটি শ্রেণিতে সংরক্ষণ করা হয় এগুলি এড়াতে দুটি উপায় রয়েছে:

  1. বেনামের পরিবর্তে একটি উদাহরণ পদ্ধতি ব্যবহার করুন।

  2. ল্যাম্বডা এক্সপ্রেশন তৈরিতে দুটি পদ্ধতিতে বিভক্ত করুন।


30
এই ক্যাপচারটি এড়ানোর সম্ভাব্য উপায়গুলি কী কী?
splintor

2
এই দুর্দান্ত উত্তরের জন্য ধন্যবাদ - আমি শিখেছি যে অজ্ঞাতনামা পদ্ধতিটি শুধুমাত্র একটি জায়গায় ব্যবহৃত হলেও ব্যবহার করার কারণ রয়েছে।
স্কটরহে

1
@ স্প্লিন্টর প্রতিনিধিটির অভ্যন্তরে অবজেক্টটি ইনস্ট্যান্ট করুন বা পরিবর্তে এটি প্যারামিটার হিসাবে পাস করুন। উপরের ক্ষেত্রে, যতদূর আমি বলতে পারি, পছন্দসই আচরণটি আসলে Randomউদাহরণের একটি রেফারেন্স রাখা , যদিও।
কেসি

2
@ এমডেন্ড্রোকেট সঠিক, এই মুহুর্তে আমরা কোড শৈলী এবং পাঠযোগ্যতার সাথে কথা বলছি। একটি ক্ষেত্র সম্পর্কে তর্ক করা সহজ। যদি মেমরির চাপ বা অবজেক্টের লাইফটাইমগুলি গুরুত্বপূর্ণ হয় তবে আমি ক্ষেত্রটি বেছে নিয়েছিলাম, অন্যথায় আমি এটিকে আরও সংক্ষিপ্ত বন্ধে রেখে যেতে চাইতাম।
yzorg

1
আমার কেস (ভারী) সরলীকৃতভাবে একটি ফ্যাক্টরি পদ্ধতিতে সিদ্ধ করা হয়েছে যা একটি ফু এবং একটি বার তৈরি করে। এটি তখন সেই দুটি বস্তুর দ্বারা প্রকাশিত ইভেন্টগুলিতে ল্যাম্বাসকে ক্যাপচার করে এবং আশ্চর্য অবাক করে, ফু ইভেন্টটি বার ইভেন্টের লাম্বা থেকে জীবন্ত এবং বিপরীতভাবে ক্যাপচারগুলি রাখে। আমি সি ++ থেকে এসেছি যেখানে এই পদ্ধতির কাজটি ঠিকঠাক হত, এবং নিয়মগুলি এখানে আলাদা ছিল তা খুঁজে পেয়ে একটু অবাক হয়েই গিয়েছিলাম। আপনি যত বেশি জানেন, আমার ধারণা।
dlf

35

পিটার মর্টেনসেনের সাথে একমত।

সি # সংকলকটি শুধুমাত্র একটি প্রকার উত্পন্ন করে যা কোনও পদ্ধতিতে সমস্ত ল্যাম্বদা এক্সপ্রেশনগুলির জন্য সমস্ত ভেরিয়েবলকে encapsulates।

উদাহরণস্বরূপ, উত্স কোড দেওয়া:

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

সতর্কতাটি বৈধ এবং একাধিক ল্যাম্বদা রয়েছে এমন পদ্ধতিগুলিতে প্রদর্শিত হয় এবং তারা বিভিন্ন মান গ্রহণ করে

যখন ল্যাম্বডাসযুক্ত একটি পদ্ধতি আহ্বান করা হয়, তখন একটি সংকলক উত্পাদিত অবজেক্ট এর সাথে ইনস্ট্যান্ট করা হয়:

  • ল্যাম্বডাস উপস্থাপন উদাহরণ পদ্ধতি
  • এই ল্যাম্বডাস যে কোনও দ্বারা ক্যাপচার করা সমস্ত মানকে উপস্থাপন করে ক্ষেত্র

উদাহরণ হিসাবে:

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উভয়ই নোট করুন ।p1p2

কল্পনা করুন যে:

  • 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

লিনক থেকে এসকিএল অনুসন্ধানগুলির জন্য, আপনি এই সতর্কতাটি পেতে পারেন। ল্যাবড্ডার স্কোপটি পদ্ধতির আওতার বাইরে চলে যাওয়ার পরে প্রায়শই ক্যোয়ারীটি বাস্তবে বাস্তবায়িত হওয়ার কারণে পদ্ধতিটি বহির্ভূত হতে পারে। আপনার পরিস্থিতির উপর নির্ভর করে, আপনি L2S ল্যাম্বডায় ক্যাপচার হওয়া পদ্ধতির উদাহরণস্বরূপ ভারগুলিতে জিসির জন্য অনুমতি দেওয়ার জন্য পদ্ধতির মধ্যে ফলাফলগুলি (যেমন .ToList () মাধ্যমে) বাস্তবায়িত করতে চাইতে পারেন।


2

নীচে দেখানো মত ইঙ্গিতগুলিতে ক্লিক করে আপনি সর্বদা আর # টি পরামর্শের কারণ নিয়ে বের করতে পারেন:

এখানে চিত্র বর্ণনা লিখুন

এই ইঙ্গিতটি আপনাকে এখানে পরিচালনা করবে ।


এই পরিদর্শনটি আপনার দৃষ্টি আকর্ষণ করে যে স্পষ্টত দৃশ্যমানের চেয়ে আরও বেশি বন্ধের মান ধরা হচ্ছে যা এর মূল্যবোধগুলির জীবনকালকে প্রভাবিত করে।

নিম্নলিখিত কোড বিবেচনা করুন:

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

প্রথম বন্ধে আমরা দেখতে পাচ্ছি যে আপত্তি 1 এবং আপত্তি 2 উভয়ই সুস্পষ্টভাবে বন্দী হয়েছে; আমরা কেবল কোডটি দেখে এটি দেখতে পারি। দ্বিতীয় বন্ধের জন্য আমরা দেখতে পাচ্ছি যে আপত্তি 1 সুস্পষ্টভাবে ক্যাপচার করা হচ্ছে, তবে রিশার্পার আমাদের সতর্ক করে দিচ্ছেন যে আপত্তি 2 সুস্পষ্টভাবে ক্যাপচার করা হচ্ছে।

এটি সি # সংকলকটিতে প্রয়োগের বিশদের কারণে is সংকলনের সময় ক্লোজারগুলি আবার ক্ষেত্রগুলির সাথে ক্লাসে আবার লিখিত হয় যা ক্যাপচারিত মানগুলি ধরে রাখে এবং সেই পদ্ধতিগুলি যা ক্লোজারকেই উপস্থাপন করে। সি # সংকলকটি প্রতি পদ্ধতি অনুসারে কেবলমাত্র এই জাতীয় একটি ক্লাস তৈরি করবে এবং যদি কোনও পদ্ধতিতে একাধিক বন্ধের সংজ্ঞা দেওয়া হয়, তবে এই শ্রেণিতে একাধিক পদ্ধতি থাকবে, প্রতিটি বন্ধের জন্য একটি করে এবং এতে সমস্ত ক্লোজার থেকে সমস্ত ক্যাপচার করা মান অন্তর্ভুক্ত থাকবে।

সংকলকটি যে কোডটি উত্পন্ন করে তা যদি আমরা দেখি তবে এটি দেখতে কিছুটা এ জাতীয় দেখতে পাওয়া যায় (কিছু নাম পড়া সহজ করার জন্য পরিষ্কার করা হয়েছে):

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

যখন পদ্ধতিটি চলে, তখন এটি প্রদর্শন ক্লাস তৈরি করে, যা সমস্ত ক্লোজারের জন্য সমস্ত মানকে ক্যাপচার করে। সুতরাং বন্ধের কোনওটিতে যদি কোনও মান ব্যবহার না করা হয়, তবুও এটি ধরা পড়বে। রিশার্পার হাইলাইট করছে এটি এটিই "অন্তর্নিহিত" ক্যাপচার।

এই পরিদর্শনটির অর্থ এই যে অবসন্নভাবে ক্যাপচার হওয়া মানটি আবর্জনা সংগ্রহ করা হবে না যতক্ষণ না বন্ধটি নিজেই আবর্জনা সংগ্রহ করা হয়। এই মানটির জীবদ্দশায় এখন বন্ধের জীবনকাল বেঁধে দেওয়া হয়েছে যা মানটি স্পষ্টভাবে ব্যবহার করে না। যদি বন্ধটি দীর্ঘকাল বেঁচে থাকে তবে এটি আপনার কোডে নেতিবাচক প্রভাব ফেলতে পারে, বিশেষত যদি ক্যাপচার মানটি খুব বেশি থাকে large

মনে রাখবেন যে এটি সংকলকের বাস্তবায়ন বিশদ হিসাবে, এটি মাইক্রোসফ্ট (প্রাক এবং পোস্ট রোজলিন) বা মনো এর সংকলক হিসাবে সংস্করণ এবং বাস্তবায়নের সাথে সামঞ্জস্যপূর্ণ। একটি মান ধরণের ক্যাপচারটি একাধিক বন্ধকে সঠিকভাবে পরিচালনা করতে প্রয়োগের অবশ্যই বর্ণিত হিসাবে কাজ করতে হবে। উদাহরণস্বরূপ, যদি একাধিক বন্ধগুলি কোনও ইনট ক্যাপচার করে, তবে তাদের অবশ্যই একই উদাহরণটি ক্যাপচার করতে হবে, যা কেবলমাত্র একটি একক ভাগ করা বেসরকারী নেস্টেড ক্লাসের সাথেই ঘটতে পারে। এর পার্শ্ব প্রতিক্রিয়া হ'ল সমস্ত ক্যাপচারিত মানগুলির জীবনকাল হ'ল যে কোনও মান বন্ধ করে দেওয়ার সর্বোচ্চ আজীবন lifetime

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.