संगति-एक तरफ, क्या यह समझ में नहीं आता कि हमारे कोड को रिफलेक्टर की आवश्यकता के बिना त्रुटि से निपटने में सक्षम होना चाहिए?
इसका उत्तर देने के लिए, केवल एक चर के दायरे को देखना आवश्यक है ।
यहां तक कि अगर चर दायरे में रहता है, तो भी इसे निश्चित रूप से नहीं सौंपा जाएगा ।
ट्रायल ब्लॉक में वैरिएबल को डिक्लेयर करना - कंपाइलर को, और ह्यूमन रीडर्स को - कि यह उस ब्लॉक के अंदर ही सार्थक है। संकलक के लिए यह लागू करना उपयोगी है।
यदि आप चाहते हैं कि ट्रायल ब्लॉक के बाद वैरिएबल गुंजाइश में हो, तो आप इसे ब्लॉक के बाहर घोषित कर सकते हैं:
var zerothVariable = 1_000_000_000_000L;
int firstVariable;
try {
// Change checked to unchecked to allow the overflow without throwing.
firstVariable = checked((int)zerothVariable);
}
catch (OverflowException e) {
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
}
यह व्यक्त करता है कि चर कोशिश ब्लॉक के बाहर सार्थक हो सकता है। संकलक यह अनुमति देगा।
लेकिन यह एक और कारण भी दिखाता है कि आमतौर पर एक कोशिश ब्लॉक में उन्हें पेश करने के बाद चर को दायरे में रखना उपयोगी नहीं होगा। C # कंपाइलर निश्चित असाइनमेंट विश्लेषण करता है और एक वैरिएबल के मूल्य को पढ़ने पर रोक लगाता है, जो यह साबित नहीं हुआ है कि उसे एक मान दिया गया है। तो आप अभी भी चर से नहीं पढ़ सकते हैं।
मान लीजिए कि मैं प्रयास खंड के बाद चर से पढ़ने का प्रयास करता हूं:
Console.WriteLine(firstVariable);
यह एक संकलन-समय त्रुटि देगा :
CS0165 अप्रभावित स्थानीय चर 'FirstVariable' का उपयोग
मैंने एनवायरनमेंट को कॉल किया। कैच ब्लॉक में, इसलिए मुझे पता है कि वेरिएबल को Console.WriteLine पर कॉल करने से पहले असाइन किया गया है। लेकिन संकलक यह अनुमान नहीं लगाता है।
कंपाइलर इतना सख्त क्यों है?
मैं यह भी नहीं कर सकता:
int n;
try {
n = 10; // I know this won't throw an IOException.
}
catch (IOException) {
}
Console.WriteLine(n);
इस प्रतिबंध को देखने का एक तरीका यह है कि C # में निश्चित असाइनमेंट विश्लेषण बहुत परिष्कृत नहीं है। लेकिन इसे देखने का एक और तरीका यह है कि, जब आप कैच क्लॉस के साथ एक कोशिश ब्लॉक में कोड लिखते हैं, तो आप संकलक और किसी भी मानव पाठक दोनों को बता रहे हैं कि इसे ऐसे व्यवहार किया जाना चाहिए जैसे यह सब नहीं चल सकता।
यह समझने के लिए कि मेरा क्या मतलब है, कल्पना करें कि कंपाइलर ने ऊपर दिए गए कोड को अनुमति दी है, लेकिन फिर आपने कोशिश ब्लॉक में एक फ़ंक्शन में एक कॉल जोड़ा है जिसे आप व्यक्तिगत रूप से जानते हैं कि कोई अपवाद नहीं होगा । यह सुनिश्चित करने में सक्षम नहीं होने के कारण कि फंक्शन को ए फेक नहीं किया गया है IOException
, कंपाइलर को पता नहीं चल सकता है कि n
असाइन किया गया था, और फिर आपको रिफ्लेक्टर करना होगा।
यह कहना है कि, यह निर्धारित करने में अत्यधिक परिष्कृत विश्लेषण द्वारा पूर्वगामी कि पकड़ खंड के साथ एक कोशिश ब्लॉक में सौंपा चर निश्चित रूप से बाद में सौंपा गया है, संकलक आपको कोड लिखने से बचने में मदद करता है जो बाद में टूटने की संभावना है। (आखिरकार, एक अपवाद को पकड़ने का मतलब आमतौर पर आपको लगता है कि किसी को फेंक दिया जा सकता है।)
आप यह सुनिश्चित कर सकते हैं कि चर को सभी कोड पथों के माध्यम से सौंपा गया है।
आप चर को कोशिश ब्लॉक से पहले या कैच ब्लॉक में मान देकर संकलित कर सकते हैं। इस तरह, यह अभी भी आरंभीकृत या नियत किया गया होगा, भले ही कोशिश ब्लॉक में असाइनमेंट न हो। उदाहरण के लिए:
var n = 0; // But is this meaningful, or just covering a bug?
try {
n = 10;
}
catch (IOException) {
}
Console.WriteLine(n);
या:
int n;
try {
n = 10;
}
catch (IOException) {
n = 0; // But is this meaningful, or just covering a bug?
}
Console.WriteLine(n);
वो संकलन। लेकिन केवल कुछ ऐसा करना सबसे अच्छा है यदि आप जो डिफ़ॉल्ट मान देते हैं वह समझ में आता है * और सही व्यवहार पैदा करता है।
ध्यान दें, इस दूसरे मामले में जहां आप कोशिश ब्लॉक में और सभी कैच ब्लॉक में वेरिएबल असाइन करते हैं, हालांकि आप ट्राइ-कैच के बाद वेरिएबल को पढ़ सकते हैं, फिर भी आप अटैच finally
ब्लॉक के अंदर वेरिएबल नहीं पढ़ पाएंगे , क्योंकि निष्पादन अधिक स्थितियों में एक कोशिश ब्लॉक को छोड़ सकता है जितना हम अक्सर सोचते हैं ।
* वैसे, कुछ भाषाएं, जैसे C और C ++, दोनों ही असिंचित चर की अनुमति देती हैं और उनमें पढ़ने से रोकने के लिए निश्चित असाइनमेंट विश्लेषण नहीं है। चूँकि एकतरफा स्मृति को पढ़ने से प्रोग्रामों के प्रति उदासीनता और अनिश्चिततापूर्ण व्यवहार होता है, इसलिए आमतौर पर उन भाषाओं में वैरिएबल शुरू करने से बचने की सिफारिश की जाती है, बिना इनिशलाइज़र की आपूर्ति के। C # और Java जैसे निश्चित असाइनमेंट विश्लेषण वाली भाषाओं में, कंपाइलर आपको अनइंस्टॉल किए गए वैरिएबल को पढ़ने से बचाता है और साथ ही उन्हें अर्थहीन मान के साथ शुरू करने की कम बुराई से बचाता है जिसे बाद में अर्थपूर्ण समझा जा सकता है।
आप इसे इतना कोड पथ बना सकते हैं जहां चर को एक अपवाद (या वापसी) नहीं दिया जाता है।
यदि आप कुछ कार्रवाई करने (लॉगिंग की तरह) करने की कोशिश करते हैं और अपवाद को फिर से उखाड़ फेंकते हैं या किसी अन्य अपवाद को फेंक देते हैं, और यह किसी भी कैच क्लॉस में होता है जहां चर नहीं सौंपा गया है, तो कंपाइलर को पता चलेगा कि चर सौंपा गया है:
int n;
try {
n = 10;
}
catch (IOException e) {
Console.Error.WriteLine(e.Message);
throw;
}
Console.WriteLine(n);
वह संकलन करता है, और अच्छी तरह से एक उचित विकल्प हो सकता है। हालांकि, एक वास्तविक आवेदन में, जब तक अपवाद केवल में फेंक दिया जाता है स्थितियों में, जहां यह और भी मतलब नहीं है ठीक करने के लिए प्रयास करने के लिए * , आप यह सुनिश्चित करें कि आप अभी भी पकड़ने रहे हैं बनाना चाहिए और ठीक से यह निपटने कहीं ।
(आप इस स्थिति में किसी अंतिम ब्लॉक में चर को नहीं पढ़ सकते हैं, लेकिन ऐसा महसूस नहीं होता है कि आपको ऐसा करने में सक्षम होना चाहिए - आखिरकार, अंततः ब्लॉक हमेशा अनिवार्य रूप से चलते हैं, और इस मामले में चर हमेशा असाइन नहीं किया जाता है ।)
* उदाहरण के लिए, कई अनुप्रयोगों में एक पकड़ क्लॉज नहीं होता है जो एक OutOfememExException को संभालता है क्योंकि वे इसके बारे में कुछ भी कर सकते हैं कम से कम दुर्घटनाग्रस्त होने के रूप में खराब हो सकते हैं ।
हो सकता है कि आप वास्तव में कोड को रिफलेक्टर करना चाहते हों।
अपने उदाहरण में, आप परिचय firstVariable
और secondVariable
कोशिश ब्लॉकों में। जैसा कि मैंने कहा है, आप उन्हें कोशिश कर सकते हैं इससे पहले कि वे असाइन किए गए ब्लॉकों को ब्लॉक करें, ताकि वे बाद में दायरे में रहें, और आप कंपाइलर को संतुष्ट / छल कर सकते हैं ताकि आप उन्हें हमेशा सुनिश्चित कर सकें कि वे हमेशा असाइन किए गए हैं।
लेकिन उन ब्लॉकों के बाद दिखाई देने वाला कोड संभवतः उन पर निर्भर करता है जिन्हें सही तरीके से सौंपा गया है। यदि ऐसा है, तो आपके कोड को प्रतिबिंबित करना चाहिए और यह सुनिश्चित करना चाहिए।
सबसे पहले, आप (और) को वास्तव में वहाँ त्रुटि को संभालना चाहिए? अपवादों को संभालने का एक कारण यह है कि त्रुटियों को संभालना आसान बनाने के लिए जहां वे प्रभावी ढंग से निपट सकते हैं , भले ही वह पास न हो जहां वे होते हैं।
यदि आप वास्तव में उस फ़ंक्शन में त्रुटि को संभाल नहीं सकते हैं जो उन चरों को आरंभीकृत और उपयोग करता है, तो शायद कोशिश ब्लॉक उस फ़ंक्शन में बिल्कुल भी नहीं होना चाहिए, लेकिन इसके बजाय कहीं अधिक है (यानी, कोड में जो उस फ़ंक्शन को कॉल करता है, या कोड। उस कोड को कॉल करता है )। बस सुनिश्चित करें कि आप गलती से कहीं और फेंके गए अपवाद को नहीं पकड़ रहे हैं और गलत तरीके से यह मानते हुए कि इसे शुरू करते समय फेंक दिया गया था firstVariable
और secondVariable
।
एक अन्य तरीका कोड को डालने का है जो कि कोशिश ब्लॉक में चर का उपयोग करता है। यह अक्सर उचित होता है। फिर, यदि आपके अपवादों को पकड़ने वाले समान अपवादों को आसपास के कोड से भी फेंका जा सकता है, तो आपको यह सुनिश्चित करना चाहिए कि जब आप उन्हें संभाल रहे हैं तो आप उस संभावना की उपेक्षा नहीं कर रहे हैं।
(मैं मान रहा हूं कि आप अपने उदाहरणों में दिखाए गए अभिव्यक्तियों की तुलना में चर के साथ चर को अधिक जटिल कर रहे हैं, जैसे कि वे वास्तव में एक अपवाद फेंक सकते हैं, और यह भी कि आप वास्तव में सभी संभावित अपवादों को पकड़ने की योजना नहीं बना रहे हैं , लेकिन सिर्फ कुछ विशिष्ट अपवादों को पकड़ने के लिए। आप अनुमान लगा सकते हैं और सार्थक रूप से संभाल सकते हैं । यह सच है कि वास्तविक दुनिया हमेशा इतनी अच्छी नहीं होती है और उत्पादन कोड कभी-कभी ऐसा होता है , लेकिन चूंकि यहां आपका लक्ष्य दो विशिष्ट चर को आरंभ करते समय होने वाली त्रुटियों को संभालना है, कोई भी पकड़ क्लॉज़ जो आप उस विशिष्ट के लिए लिखते हैं। उद्देश्य उन त्रुटियों के लिए विशिष्ट होना चाहिए जो हैं।)
तीसरा तरीका कोड को निकालने का है जो विफल हो सकता है, और कोशिश-पकड़ जो इसे संभालती है, अपने तरीके से। यह उपयोगी है यदि आप पहले पूरी तरह से त्रुटियों से निपटना चाहते हैं, और फिर अनजाने में एक अपवाद को पकड़ने के बारे में चिंता न करें जो इसके बजाय कहीं और संभाला जाना चाहिए।
उदाहरण के लिए, मान लीजिए कि आप किसी भी चर को असाइन करने में विफलता पर आवेदन तुरंत छोड़ना चाहते हैं। (स्पष्ट रूप से सभी अपवाद हैंडलिंग घातक त्रुटियों के लिए नहीं है; यह सिर्फ एक उदाहरण है, और हो सकता है कि आप इस समस्या पर प्रतिक्रिया के लिए अपना आवेदन कैसे चाहते हैं।) आप ऐसा कुछ कर सकते हैं।
// In real life, this should be named more descriptively.
private static (int firstValue, int secondValue) GetFirstAndSecondValues()
{
try {
// This code is contrived. The idea here is that obtaining the values
// could actually fail, and throw a SomeSpecificException.
var firstVariable = 1;
var secondVariable = firstVariable;
return (firstVariable, secondVariable);
}
catch (SomeSpecificException e) {
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
throw new InvalidOperationException(); // unreachable
}
}
// ...and of course so should this.
internal static void MethodThatUsesTheValues()
{
var (firstVariable, secondVariable) = GetFirstAndSecondValues();
// Code that does something with them...
}
यह कोड रिटर्न करता है और कई मानों को वापस करने के लिए C # 7.0 के सिंटैक्स के साथ एक वैल्यूटल को डिकंस्ट्रक्ट करता है, लेकिन यदि आप अभी भी C # के पुराने संस्करण पर हैं, तो भी आप इस तकनीक का उपयोग कर सकते हैं; उदाहरण के लिए, आप मापदंडों का उपयोग कर सकते हैं, या एक कस्टम ऑब्जेक्ट वापस कर सकते हैं जो दोनों मान प्रदान करता है । इसके अलावा, यदि दो चर वास्तव में कसकर संबंधित नहीं हैं, तो संभवतः दो अलग-अलग तरीकों का उपयोग करना बेहतर होगा ।
खासकर यदि आपके पास कई तरीके हैं, तो आपको अपने कोड को घातक त्रुटियों के उपयोगकर्ता को सूचित करने और छोड़ने के लिए केंद्रीयकरण पर विचार करना चाहिए। (उदाहरण के लिए, यदि आप एक लिख सकता है Die
एक साथ विधि message
पैरामीटर।) पंक्ति वास्तव में मार डाला कभी नहीं किया गया है , ताकि आप की जरूरत नहीं है (और नहीं होना चाहिए) इसके लिए एक पकड़ खंड लिखें।throw new InvalidOperationException();
जब कोई विशेष त्रुटि होती है, तो छोड़ने के अलावा, आप कभी-कभी ऐसा कोड लिख सकते हैं जो इस तरह दिखता है यदि आप किसी अन्य प्रकार के अपवाद को फेंकते हैं जो मूल अपवाद को लपेटता है । (उस स्थिति में, आपको दूसरी, पहुंच से बाहर फेंकने वाली अभिव्यक्ति की आवश्यकता नहीं होगी।)
निष्कर्ष: स्कोप तस्वीर का केवल एक हिस्सा है।
आप अपने कोड को रिफ़ेक्टरिंग के बिना त्रुटि से निपटने के प्रभाव को प्राप्त कर सकते हैं (या, यदि आप चाहें, तो शायद ही किसी भी रिफैक्टरिंग के साथ), केवल चर के घोषणाओं को उनके कार्य से अलग करके। संकलक इसे अनुमति देता है यदि आप C # के निश्चित असाइनमेंट नियमों को पूरा करते हैं, और कोशिश ब्लॉक के आगे एक चर घोषित करने से इसकी स्थिति स्पष्ट हो जाती है। लेकिन आगे फिर से रिफ्लेक्ट करना आपका सबसे अच्छा विकल्प हो सकता है।
try.. catch
एक विशिष्ट प्रकार का कोड ब्लॉक है, और जहाँ तक सभी कोड ब्लॉक जाते हैं, आप एक वेरिएबल को एक में घोषित नहीं कर सकते हैं और उस वैरिएबल को दूसरे स्कोप के रूप में उपयोग कर सकते हैं।