पैदावार क्यों नहीं पकड़ के साथ एक कोशिश ब्लॉक के अंदर दिखाई दे सकते हैं?


95

निम्नलिखित ठीक है:

try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}

finallyब्लॉक जब पूरी बात का निष्पादन पूरा होने (चलाता है IEnumerator<T>का समर्थन करता है IDisposableयह भी जब गणना छोड़ दिया जाता है सुनिश्चित करने के लिए इससे पहले कि यह खत्म एक तरीका प्रदान करने)।

लेकिन यह ठीक नहीं है:

try
{
    Console.WriteLine("Before");

    yield return 1;  // error CS1626: Cannot yield a value in the body of a try block with a catch clause

    Console.WriteLine("After");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

मान लीजिए (तर्क के लिए) कि WriteLineकोशिश ब्लॉक के अंदर एक या अन्य कॉल द्वारा एक अपवाद फेंक दिया जाता है । catchब्लॉक में निष्पादन जारी रखने में क्या समस्या है ?

बेशक, यील्ड रिटर्न पार्ट कुछ भी फेंकने में असमर्थ (वर्तमान में) है, लेकिन इससे पहले या बाद में फेंके गए अपवादों से निपटने के लिए हमें एक घेरने try/ होने से क्यों रोकना चाहिए ?catchyield return

अद्यतन: यहां एरिक लिपर्ट की एक दिलचस्प टिप्पणी है - ऐसा लगता है कि उनके पास पहले से ही पर्याप्त रूप से कोशिश / अंत में व्यवहार को सही ढंग से लागू करने में काफी समस्याएं हैं!

संपादित करें: इस त्रुटि पर MSDN पृष्ठ है: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx । यह क्यों, हालांकि समझा नहीं है।


2
करने के लिए एरिक Lippert की टिप्पणी सीधा लिंक: blogs.msdn.com/oldnewthing/archive/2008/08/14/...
रोमन Starkov

नोट: आप स्वयं या तो पकड़ ब्लॉक में उपज नहीं कर सकते:
साइमन_वेअर

2
पुरानी लिंक अब काम नहीं करती है।
सेबस्टियन रेड

जवाबों:


50

मुझे संदेह है कि यह व्यवहार्यता के बजाय व्यावहारिकता का मामला है। मुझे संदेह है कि बहुत बार, बहुत कम बार जहां यह प्रतिबंध वास्तव में एक मुद्दा है जिसके आसपास काम नहीं किया जा सकता है - लेकिन कंपाइलर में जोड़ा जटिलता बहुत महत्वपूर्ण होगा।

इस तरह की कुछ चीजें हैं जिनका मैंने पहले ही सामना किया है:

  • विशेषताएँ सामान्य नहीं हो पा रही हैं
  • XY से X के लिए अक्षमता (X में एक नेस्टेड वर्ग)
  • उत्पन्न वर्गों में सार्वजनिक क्षेत्रों का उपयोग करके Iterator ब्लॉक

इन मामलों में से प्रत्येक में संकलक में अतिरिक्त जटिलता की कीमत पर थोड़ी अधिक स्वतंत्रता प्राप्त करना संभव होगा। टीम ने व्यावहारिक विकल्प बनाया, जिसके लिए मैं उनकी सराहना करता हूं - मेरे पास 99.9% सटीक संकलक के साथ थोड़ी अधिक प्रतिबंधात्मक भाषा होगी (हाँ, बग हैं; मैं एक एसओ में दूसरे दिन बस चला गया) एक से अधिक लचीली भाषा जो सही ढंग से संकलित नहीं हो सकी।

संपादित करें: यहाँ एक छद्म सबूत है कि यह कैसे संभव है, यह कैसे संभव है।

उस पर विचार करे:

  • आप यह सुनिश्चित कर सकते हैं कि पैदावार वापसी का हिस्सा खुद एक अपवाद नहीं फेंकता है (मूल्य को पूर्व निर्धारित करता है, और फिर आप बस एक फ़ील्ड सेट कर रहे हैं और "सच" लौटा रहे हैं)
  • आपको ऐसा प्रयास करने / पकड़ने की अनुमति दी जाती है जो किसी इट्रेटर ब्लॉक में उपज वापसी का उपयोग नहीं करता है।
  • इटरेटर ब्लॉक के सभी स्थानीय चर जेनरेट किए गए प्रकार में उदाहरण चर हैं, इसलिए आप कोड को नए तरीकों से स्वतंत्र रूप से स्थानांतरित कर सकते हैं

अब रूपांतरित करें:

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

में (छद्म कोड की तरह):

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    __current = 10;
    return true;

case just_after_yield_return:
    try
    {
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        CatchBlock();
    }
    goto case post;

case post;
    Console.WriteLine("Post");


void CatchBlock()
{
    Console.WriteLine("Catch block");
}

केवल दोहराव कोशिशों को पकड़ने / ब्लॉकों को स्थापित करने में है - लेकिन ऐसा कुछ है जो कंपाइलर निश्चित रूप से कर सकता है।

मैं अच्छी तरह से यहाँ कुछ याद किया हो सकता है - यदि हां, तो कृपया मुझे बताएं!


11
एक अच्छी प्रूफ-ऑफ-कॉन्सेप्ट, लेकिन यह रणनीति तब तक दर्दनाक हो जाती है (जब आप C # कंपाइलर लेखक की तुलना में C # प्रोग्रामर के लिए अधिक हो जाते हैं) एक बार जब आप जैसी चीजों के साथ स्कोप बनाना शुरू करते हैं usingऔर foreach। उदाहरण के लिए:try{foreach (string s in c){yield return s;}}catch(Exception){}
ब्रायन

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

7
"मुझे संदेह है कि बहुत कम हैं, बहुत कम बार जहां यह प्रतिबंध वास्तव में एक मुद्दा है जिसके चारों ओर काम नहीं किया जा सकता है" यह इस तरह की बात है कि आपको अपवादों की आवश्यकता नहीं है क्योंकि आप आमतौर पर सी में उपयोग किए जाने वाले त्रुटि कोड वापसी की रणनीति का उपयोग कर सकते हैं। बहुत साल पहले। मैं मानता हूं कि तकनीकी कठिनाइयाँ महत्वपूर्ण हो सकती हैं, लेकिन यह अभी भी yieldमेरी राय में इसकी उपयोगिता को गंभीर रूप से सीमित कर देती है , क्योंकि स्पेगेटी कोड के कारण आपको इसके चारों ओर काम करने के लिए लिखना होगा।
jpmc26

@ jpmc26: नहीं, यह वास्तव में ऐसा कहने जैसा नहीं है। मुझे याद नहीं है कि यह मुझे कभी काटता है, और मैंने कई बार इट्रेटर ब्लॉक का उपयोग किया है। यह IMO की उपयोगिता को थोड़ा सीमित करता है yield- यह गंभीर रूप से लंबा रास्ता है ।
जॉन स्कीट

2
इस 'सुविधा' को वास्तव में वर्कअराउंड करने के लिए कुछ मामलों में कुछ बल्कि बदसूरत कोड की आवश्यकता होती है, देखें stackoverflow.com/questions/5067188/…
namey

5

yieldइट्रेटर परिभाषा में सभी स्टेटमेंट स्टेट मशीन में एक स्टेट में परिवर्तित हो जाते हैं, जो प्रभावी रूप switchसे एडवांस स्टेट्स के लिए स्टेटमेंट का उपयोग करता है। अगर उसने एक कोशिश / कैच में बयानों के लिए कोड उत्पन्न किया है तो उसे उस ब्लॉक के हर दूसरे स्टेटमेंट को छोड़कर प्रत्येक स्टेटमेंट के लिए ब्लॉक में सब कुछyield डुप्लिकेट करना होगा । यह हमेशा संभव नहीं है, खासकर अगर एक बयान पहले वाले पर निर्भर है।try yieldyieldyield


2
मुझे नहीं लगता कि मैं इसे खरीदता हूं। मुझे लगता है कि यह पूरी तरह से संभव होगा - लेकिन बहुत जटिल।
जॉन स्कीट

2
C # में ब्लॉक पकड़ने / आज़माने का मतलब दोबारा प्रवेश नहीं करना है। यदि आप उन्हें विभाजित करते हैं तो एक अपवाद के बाद MoveNext () को कॉल करना संभव है और संभवतः अमान्य स्थिति वाले प्रयास ब्लॉक को जारी रखें।
मार्क सिडेड

2

मैं अनुमान लगाता हूँ कि जब आप किसी एन्यूमरेटर से रिटर्न प्राप्त करते हैं तो कॉल स्टैक घाव / अनचाहा हो जाता है क्योंकि यह वास्तव में अपवाद को "पकड़ने" के लिए एक कोशिश / कैच ब्लॉक के लिए असंभव हो जाता है। (क्योंकि यील्ड रिटर्न ब्लॉक स्टैक पर नहीं है, भले ही वह पुनरावृत्ति ब्लॉक उत्पन्न करता है)

क्या मैं सेटअप के बारे में बात कर रहा हूँ की एक विचारधारा पाने के लिए एक पुनरावृत्ति ब्लॉक और उस पुनरावृत्ति का उपयोग कर एक foreach। जांच करें कि कॉल स्टैक फ़ॉरच ब्लॉक के अंदर कैसा दिखता है और फिर इट्रेटर ट्राइ / इंसर्ट ब्लॉक के अंदर चेक करें।


मैं सी ++ में स्टैक अनइंडिंग से परिचित हूं, जहां विध्वंसक स्थानीय वस्तुओं पर कॉल किए जाते हैं जो दायरे से बाहर जाते हैं। C # में संबंधित चीज कोशिश / अंत में होगी। लेकिन जब पैदावार की वापसी होती है, तो यह नहीं होता है। और कोशिश / पकड़ के लिए इसके लिए उपज वापसी के साथ बातचीत करने की कोई आवश्यकता नहीं है।
डैनियल इयरविकर

जाँच करें कि एक
इटैलर

@ Radu094: नहीं, मुझे यकीन है कि यह संभव होगा। यह मत भूलो कि यह पहले से ही अंत में संभालता है, जो कम से कम कुछ समान है।
जॉन स्कीट

2

जब तक Microsoft का कोई व्यक्ति इस विचार पर ठंडा पानी डालने के लिए नहीं आता, मैंने द इनकाउंटर SKEET का जवाब स्वीकार कर लिया है। लेकिन मैं इस बात से सहमत नहीं हूँ कि यह एक निश्चित कंपाइलर है, लेकिन एक # कंपाइलर पहले से ही इस बदलाव को सुलझाने में बहुत ही चतुर है, जहाँ तक यह हमारे लिए है। इस मामले में थोड़ी अधिक पूर्णता भाषा को कम धार वाले मामलों या गोच के साथ उपयोग करने, सिखाने, समझाने में आसान बनाती है। इसलिए मुझे लगता है कि यह अतिरिक्त प्रयास के लायक होगा। रेडमंड में कुछ लोग एक पखवाड़े के लिए अपने सिर को खरोंचते हैं, और परिणामस्वरूप अगले दशक में लाखों कोडर्स थोड़ा और आराम कर सकते हैं।

(मैं यह भी कि वहाँ yield returnएक अपवाद को फेंकने का एक तरीका है कि राज्य मशीन में भर दिया गया है "बाहर से", कोड ड्राइविंग द्वारा कोड बनाने के लिए एक घिनौनी इच्छा को परेशान करता है। लेकिन यह चाहने के मेरे कारण काफी अस्पष्ट हैं।

वास्तव में जॉन के उत्तर के बारे में मेरे पास एक प्रश्न है कि उपज वापसी अभिव्यक्ति फेंकने के साथ क्या करना है।

जाहिर है कि यील्ड रिटर्न 10 इतना खराब नहीं है। लेकिन यह बुरा होगा:

yield return File.ReadAllText("c:\\missing.txt").Length;

तो इससे पहले कि यह कोशिश न हो कि इस मूल्यांकन के अंदर अधिक जानकारी नहीं होगी / ब्लॉक को पकड़ें:

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
        __current = File.ReadAllText("c:\\missing.txt").Length;
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    return true;

अगली समस्या होगी नेस्टेड ट्रायल / कैच ब्लॉक्स और अपवादों को फिर से पकड़ना:

try
{
    Console.WriteLine("x");

    try
    {
        Console.WriteLine("a");
        yield return 10;
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        Console.WriteLine("y");

        if ((DateTime.Now.Second % 2) == 0)
            throw;
    }
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

लेकिन मुझे यकीन है कि यह संभव है ...


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