यह कोड "संभावित अशक्त संदर्भ रिटर्न" संकलक चेतावनी क्यों देता है?


70

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

using System;

#nullable enable

namespace Demo
{
    public sealed class TestClass
    {
        public string Test()
        {
            bool isNull = _test == null;

            if (isNull)
                return "";
            else
                return _test; // !!!
        }

        readonly string _test = "";
    }
}

जब मैं इसका निर्माण करता हूं, तो चिह्नित लाइन !!!एक संकलक चेतावनी देती है warning CS8603: Possible null reference return.:।

मुझे यह थोड़ा भ्रमित लगता है, यह देखते हुए कि _testयह गैर-शून्य है।

यदि मैं कोड को निम्नलिखित में बदलता हूं, तो चेतावनी दूर हो जाती है:

        public string Test()
        {
            // bool isNull = _test == null;

            if (_test == null)
                return "";
            else
                return _test;
        }

क्या कोई इस व्यवहार की व्याख्या कर सकता है?


1
डीबग.एजर्स अप्रासंगिक है क्योंकि यह एक रनटाइम चेक है, जबकि कंपाइलर चेतावनी एक संकलन समय की जांच है। संकलक के पास रनटाइम व्यवहार की पहुंच नहीं है।
पॉलीफ़ुन

5
The Debug.Assert is irrelevant because that is a runtime check- यह है प्रासंगिक है क्योंकि अगर आपको लगता है कि लाइन बाहर टिप्पणी, चेतावनी चली जाती है।
मैथ्यू वॉटसन

1
@ पॉलीफ़न: संकलक संभावित रूप से जान सकता है (विशेषताओं के माध्यम से) जो Debug.Assertपरीक्षण विफल होने पर एक अपवाद को फेंक देगा।
जॉन स्कीट

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

2
@EricLippert: Debug.Assertअब हालत पैरामीटर के लिए एनोटेशन ( src ) है DoesNotReturnIf(false)
जॉन स्कीट

जवाबों:


38

अशक्त प्रवाह विश्लेषण चर की अशक्त स्थिति को ट्रैक करता है, लेकिन यह अन्य राज्य को ट्रैक नहीं करता है, जैसे कि boolचर का मान ( isNullऊपर), और यह अलग-अलग चर (जैसे isNullऔर _test) की स्थिति के बीच के रिश्ते को ट्रैक नहीं करता है ।

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

ऐसा कुछ नहीं है जो हम सीधे सी # कंपाइलर में कर सकते हैं। अशक्त चेतावनी के नियम काफी परिष्कृत हैं (जैसा कि जॉन के विश्लेषण से पता चलता है!), लेकिन वे नियम हैं, और इसके बारे में तर्क दिया जा सकता है।

जैसा कि हम इस सुविधा को रोल आउट करते हैं, ऐसा लगता है कि हम ज्यादातर सही संतुलन पर चलते हैं, लेकिन कुछ स्थान हैं जो अजीब हैं, और हम सी # 9.0 के लिए उन पर दोबारा गौर करेंगे।


3
आप जानते हैं कि आप विनिर्देश में जाली सिद्धांत डालना चाहते हैं; जाली सिद्धांत भयानक है और भ्रामक नहीं है! कर दो! :)
एरिक लिपर्ट

7
आपको पता है कि आपका सवाल वैध है जब C # के लिए प्रोग्राम मैनेजर जवाब देता है!
सैम रूबी

1
@TanveerBadar: जाली सिद्धांत उन मूल्यों के सेट के विश्लेषण के बारे में है जिनके आंशिक आदेश हैं; प्रकार एक अच्छा उदाहरण हैं; यदि टाइप X का मान Y के एक वेरिएबल के लिए असाइन किया गया है, तो इसका मतलब है कि X धारण करने के लिए Y "काफी बड़ा" है, और यह एक जाली बनाने के लिए पर्याप्त है, जो तब हमें बताता है कि कंपाइलर में असाइन करने योग्यता की जाँच की जा सकती है जाली सिद्धांत के संदर्भ में विनिर्देशन में। यह स्थैतिक विश्लेषण के लिए प्रासंगिक है क्योंकि एक प्रकार के असाइनमेंट के अलावा एक विश्लेषक के लिए रुचि के कई विषय भी अक्षांशों के संदर्भ में स्पष्ट हैं।
एरिक लिपर्ट

1
@TanveerBadar: lara.epfl.ch/w/_media/sav08:schwartzbach.pdf में कुछ अच्छे परिचयात्मक उदाहरण हैं कि कैसे स्थैतिक विश्लेषण इंजन जाली सिद्धांत का उपयोग करते हैं।
एरिक लिपर्ट

1
@EricLippert बहुत बढ़िया आपको वर्णन करना शुरू नहीं करता है। यह लिंक अभी मेरी सूची में पढ़ा जाना चाहिए।
तनवीर बदर

56

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

यह उत्तर कुछ हद तक कथात्मक रूप में है, बजाय इसके कि "यहाँ के निष्कर्ष" ... मुझे आशा है कि यह इस तरह से अधिक उपयोगी है।

मैं खेतों से छुटकारा पाकर उदाहरण को थोड़ा सरल करने जा रहा हूं, और इन दो हस्ताक्षरों में से एक के साथ एक विधि पर विचार करूंगा:

public static string M(string? text)
public static string M(string text)

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

नीचे वर्णित प्रत्येक मामले में, हम विभिन्न काम करेंगे लेकिन वापस लौटने की कोशिश करेंगे text- इसलिए यह textमहत्वपूर्ण है।

बिना शर्त वापसी

पहले, चलो इसे सीधे वापस करने का प्रयास करें:

public static string M1(string? text) => text; // Warning
public static string M2(string text) => text;  // No warning

अब तक, इतना सरल। विधि की शुरुआत में पैरामीटर की अशक्त स्थिति "शायद अशक्त" है यदि यह प्रकार की है string?और "शून्य" नहीं है यदि यह प्रकार की है string

साधारण सशर्त वापसी

अब ifकथन स्थिति के भीतर अशक्त की जाँच करें । (मैं सशर्त ऑपरेटर का उपयोग करूंगा, जो मेरा मानना ​​है कि इसका एक ही प्रभाव होगा, लेकिन मैं प्रश्न के प्रति चिंतित रहना चाहता था।)

public static string M3(string? text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

public static string M4(string text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

महान, इसलिए यह एक ifबयान के भीतर की तरह दिखता है जहां स्थिति स्वयं अशक्तता की जांच करती है, ifकथन की प्रत्येक शाखा के भीतर चर की स्थिति अलग हो सकती है: elseब्लॉक के भीतर , राज्य कोड के दोनों टुकड़ों में "शून्य नहीं" है। तो विशेष रूप से, एम 3 में राज्य "शायद अशक्त" से "शून्य नहीं" में बदल जाता है।

स्थानीय चर के साथ सशर्त वापसी

अब उस स्थिति को एक स्थानीय चर पर फहराने की कोशिश करते हैं:

public static string M5(string? text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

public static string M6(string text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

एम 5 और एम 6 दोनों चेतावनी जारी करते हैं। इसलिए न केवल हमें M5 में "शायद अशक्त" से "नहीं अशक्त" से राज्य परिवर्तन का सकारात्मक प्रभाव नहीं मिलता है (जैसा कि हमने M3 में किया था) ... हम M6 में विपरीत प्रभाव प्राप्त करते हैं , जहां राज्य "से जाता है" अशक्त नहीं "से" शायद अशक्त "। जिसने वास्तव में मुझे चौंका दिया।

तो ऐसा लगता है कि हमने सीखा है कि:

  • "स्थानीय चर की गणना कैसे की जाती है" के आसपास तर्क राज्य की जानकारी के प्रचार के लिए उपयोग नहीं किया जाता है। उस पर और बाद में।
  • एक शून्य तुलना का परिचय संकलक को चेतावनी दे सकता है कि यह कुछ पहले से सोचा था कि अशक्त सब के बाद शून्य हो सकता है।

एक उपेक्षित तुलना के बाद बिना शर्त वापसी

बिना शर्त वापसी से पहले तुलना शुरू करके उन बुलेट बिंदुओं में से दूसरे को देखें। (इसलिए हम तुलना के परिणाम की पूरी तरह से अनदेखी कर रहे हैं।):

public static string M7(string? text)
{
    bool ignored = text is null;
    return text; // Warning
}

public static string M8(string text)
{
    bool ignored = text is null;
    return text; // Warning
}

ध्यान दें कि एम 8 कैसा लगता है, यह एम 2 के बराबर होना चाहिए - दोनों में एक शून्य-शून्य पैरामीटर है जो वे बिना शर्त वापस लौटते हैं - लेकिन अशक्त के साथ तुलना की शुरूआत राज्य को "नहीं अशक्त" से "शायद अशक्त" में बदल देती है। हम इस textशर्त के आगे डीरेसीफिकेशन का प्रयास करके इसके और प्रमाण प्राप्त कर सकते हैं:

public static string M9(string text)
{
    int length1 = text.Length;   // No warning
    bool ignored = text is null;
    int length2 = text.Length;   // Warning
    return text;                 // No warning
}

ध्यान दें कि returnबयान में अब चेतावनी कैसे नहीं है: निष्पादित करने के बाद की स्थिति text.Length"शून्य नहीं है" (क्योंकि यदि हम उस अभिव्यक्ति को सफलतापूर्वक निष्पादित करते हैं, तो यह अशक्त नहीं हो सकता है)। तो textपैरामीटर अपने प्रकार के कारण "शून्य नहीं" के रूप में शुरू होता है, अशक्त तुलना के कारण "शायद अशक्त" हो जाता है, फिर बाद में "शून्य नहीं" हो जाता है text2.Length

क्या तुलना राज्य को प्रभावित करती है?

तो यह एक तुलना है text is null... क्या तुलना समान प्रभाव है? यहाँ चार और विधियाँ हैं, सभी एक गैर-अशक्त स्ट्रिंग पैरामीटर से शुरू होती हैं:

public static string M10(string text)
{
    bool ignored = text == null;
    return text; // Warning
}

public static string M11(string text)
{
    bool ignored = text is object;
    return text; // No warning
}

public static string M12(string text)
{
    bool ignored = text is { };
    return text; // No warning
}

public static string M13(string text)
{
    bool ignored = text != null;
    return text; // Warning
}

भले ही x is objectअब यह एक अनुशंसित विकल्प है x != null, लेकिन उनके पास एक ही प्रभाव नहीं है: केवल शून्य (किसी भी is, ==या !=) के साथ तुलना राज्य को "नहीं अशक्त" से "शायद अशक्त" में बदल देती है।

क्यों फहराने से हालत पर असर पड़ता है?

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

public static int X1()
{
    if (true)
    {
        return 1;
    }
}

public static int X2()
{
    bool alwaysTrue = true;
    if (alwaysTrue)
    {
        return 1;
    }
    // Error: not all code paths return a value
}

भले ही हम जानते हैं कि alwaysTrueयह हमेशा सच होगा, लेकिन यह विनिर्देश में उन आवश्यकताओं को पूरा नहीं करता है ifजो कथन को अनुपलब्ध करने के बाद कोड बनाते हैं , जो कि हमें चाहिए।

यहाँ एक और उदाहरण है, निश्चित असाइनमेंट के आसपास:

public static void X3()
{
    string x;
    bool condition = DateTime.UtcNow.Year == 2020;
    if (condition)
    {
        x = "It's 2020.";
    }
    if (!condition)
    {
        x = "It's not 2020.";
    }
    // Error: x is not definitely assigned
    Console.WriteLine(x);
}

भले ही हम जानते हैं कि कोड उन ifविवरण निकायों में से एक में प्रवेश करेगा , लेकिन उस काम के लिए कुछ भी नहीं है। स्टेटिक विश्लेषण टूल अच्छी तरह से ऐसा करने में सक्षम हो सकता है, लेकिन यह कहने की कोशिश कर रहा है कि भाषा विनिर्देश में एक बुरा विचार होगा, IMO - स्थैतिक विश्लेषण टूल के लिए यह ठीक है कि सभी प्रकार की विधियां हैं जो समय के साथ विकसित हो सकती हैं, लेकिन इतना नहीं एक भाषा विनिर्देश के लिए।


7
महान विश्लेषण जॉन। कवरटी चेकर का अध्ययन करते समय मैंने जो महत्वपूर्ण बात सीखी, वह यह है कि यह कोड अपने लेखकों की मान्यताओं का प्रमाण है । जब हम एक शून्य जांच देखते हैं जो हमें सूचित करना चाहिए कि कोड के लेखकों का मानना ​​है कि चेक आवश्यक था। चेकर वास्तव में सबूतों की तलाश में है कि लेखकों का विश्वास असंगत था क्योंकि यह वह स्थान है जहां हम असंगत मान्यताओं को देखते हैं, कहते हैं, अशक्तता, कि कीड़े होते हैं।
एरिक लिपपर्ट

6
जब हम उदाहरण के लिए देखते हैं if (x != null) x.foo(); x.bar();तो हमारे पास दो सबूत होते हैं; यह ifकथन प्रस्ताव का प्रमाण है "लेखक का मानना ​​है कि फू को कॉल करने से पहले x शून्य हो सकता है" और निम्न कथन "लेखक का मानना ​​है कि x को कॉल करने से पहले शून्य नहीं है" और इस विरोधाभास की ओर जाता है निष्कर्ष है कि एक बग है। बग या तो अनावश्यक अशक्त जांच का अपेक्षाकृत सौम्य बग है, या संभावित दुर्घटनाग्रस्त बग है। कौन सा बग असली बग है यह स्पष्ट नहीं है, लेकिन यह स्पष्ट है कि एक है।
एरिक लिपर्ट

1
समस्या है कि अपेक्षाकृत अपरिष्कृत चेकर्स जो स्थानीय लोगों के अर्थों को ट्रैक नहीं करते हैं, और "झूठे रास्तों" को पसंद नहीं करते हैं - प्रवाह के पथों को नियंत्रित करते हैं जो मनुष्य आपको बता सकते हैं कि वे असंभव हैं - झूठी सकारात्मक का उत्पादन ठीक से करते हैं क्योंकि उन्होंने सटीक रूप से मॉडलिंग नहीं की है लेखकों की मान्यताएं। यह मुश्किल सा है!
एरिक लिपर्ट

3
"ऑब्जेक्ट" है, "{}" और "! = Null" के बीच असंगतता एक आइटम है जिसकी हम पिछले कुछ हफ्तों से आंतरिक रूप से चर्चा कर रहे हैं। भविष्य में एलडीएम में इसे लाने के लिए यह तय करने के लिए जा रहे हैं कि क्या हमें शुद्ध अशक्त जांच के रूप में विचार करने की आवश्यकता है या नहीं (जो व्यवहार को सुसंगत बनाता है)।
जरीदपार

1
@ArnonAxelrod का कहना है कि यह अशक्त होने का मतलब नहीं है । यह अभी भी अशक्त हो सकता है, क्योंकि अशक्त संदर्भ प्रकार केवल एक संकलक संकेत हैं। (उदाहरण: M8 (null!); या इसे C # 7 कोड से कॉल करना, या चेतावनी को अनदेखा करना।) यह बाकी प्लेटफ़ॉर्म के प्रकार की सुरक्षा की तरह नहीं है।
जॉन स्कीट

29

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

मुझे प्रवाह चेकर के कार्यान्वयन का कोई विशेष ज्ञान नहीं है, लेकिन अतीत में समान कोड के कार्यान्वयन पर काम करते हुए, मैं कुछ शिक्षित अनुमान लगा सकता हूं। फ्लो चेकर संभावित रूप से झूठे सकारात्मक मामले में दो चीजों को समर्पित कर रहा है: (1) _testअशक्त हो सकता है, क्योंकि अगर यह नहीं हो सकता है, तो आप पहले स्थान पर तुलना नहीं करेंगे, और (2) isNullसही या गलत हो सकता है - क्योंकि अगर यह नहीं हो सकता है, तो आप इसे एक में नहीं होगा if। लेकिन जो कनेक्शन return _test;केवल चलता है अगर _testवह अशक्त नहीं है, तो वह कनेक्शन नहीं बनाया जा रहा है।

यह आश्चर्यजनक रूप से मुश्किल समस्या है, और आपको उम्मीद करनी चाहिए कि संकलक को उन उपकरणों के परिष्कार को प्राप्त करने में कुछ समय लगेगा जो विशेषज्ञों द्वारा कई वर्षों से काम कर रहे हैं। उदाहरण के लिए, द कवरिटी फ्लो चेकर को यह बताने में कोई समस्या नहीं होगी कि आपके दोनों बदलावों में से किसी में भी वापसी नहीं हुई है, लेकिन कवर्सिटी फ्लो चेकर की कीमत कॉरपोरेट ग्राहकों के लिए गंभीर है।

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


"अव्यावहारिक" सही है - मैं यह क्षम्य पर विचार अगर यह सशर्त, जैसी चीजों पर ठोकर, के रूप में हम सभी जानते हैं हॉल्टिंग समस्या ऐसे मामलों में एक कठिन अखरोट का एक सा है, लेकिन तथ्य यह है वहाँ के बीच सभी में एक फर्क है कि bool b = x != nullबनाम bool b = x is { }(साथ न तो असाइनमेंट वास्तव में उपयोग किया जाता है!) दिखाता है कि यहां तक ​​कि अशक्त जांच के लिए मान्यता प्राप्त पैटर्न संदिग्ध हैं। इस काम को करने के लिए टीम की निस्संदेह कड़ी मेहनत को नापसंद नहीं करना चाहिए क्योंकि यह वास्तविक, इन-कोड कोड आधारों के लिए होना चाहिए - ऐसा लगता है कि विश्लेषण पूंजी-पी व्यावहारिक है।
जेरोइन मोस्टर्ट

@JeroenMostert: जोर्ड पार ने जॉन स्कीट के जवाब पर एक टिप्पणी में उल्लेख किया है कि Microsoft उस मुद्दे पर आंतरिक रूप से चर्चा कर रहा है।
ब्रायन

8

अन्य सभी उत्तर बहुत अधिक सटीक हैं।

किसी के उत्सुक होने की स्थिति में, मैंने कंपाइलर के तर्क को स्पष्ट रूप से https://github.com/dotnet/roslyn/issues/36927#issuecomment-508595947 में बताने की कोशिश की

जिस एक टुकड़े का उल्लेख नहीं किया गया है वह यह है कि हम यह कैसे तय करते हैं कि क्या अशक्त जांच को "शुद्ध" माना जाना चाहिए, इस अर्थ में कि यदि आप इसे करते हैं, तो हमें गंभीरता से विचार करना चाहिए कि क्या अशक्त होने की संभावना है। सी # में बहुत सारे "आकस्मिक" अशक्त चेक हैं, जहां आप कुछ और करने के एक भाग के रूप में अशक्त के लिए परीक्षण करते हैं, इसलिए हमने फैसला किया कि हम चेक के सेट को उन लोगों तक सीमित करना चाहते थे जो हमें यकीन था कि लोग जानबूझकर कर रहे थे। अनुमानी हम साथ आया था "शब्द अशक्त शामिल है", ताकि के क्यों था x != nullऔर x is objectविभिन्न परिणाम।

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