वास्तव में एक कोशिश में क्या होता है {वापसी x; } अंत में {x = null; } बयान?


259

मैंने इस टिप को एक अन्य प्रश्न में देखा और सोच रहा था कि क्या कोई मुझे समझा सकता है कि यह कैसे काम करता है?

try { return x; } finally { x = null; }

मेरा मतलब है, करता finallyखंड वास्तव में अमल के बादreturn बयान? यह कोड कितना असुरक्षित है? क्या आप किसी भी अतिरिक्त हैकरी के बारे में सोच सकते हैं जिसे इस try-finallyहैक को राइट किया जा सकता है ?

जवाबों:


235

नहीं - IL स्तर पर आप अपवाद-रहित ब्लॉक के अंदर से नहीं लौट सकते। यह अनिवार्य रूप से इसे एक चर में संग्रहीत करता है और बाद में लौटता है

इसके समान:

int tmp;
try {
  tmp = ...
} finally {
  ...
}
return tmp;

उदाहरण के लिए (रिफ्लेक्टर का उपयोग करके):

static int Test() {
    try {
        return SomeNumber();
    } finally {
        Foo();
    }
}

के लिए संकलन:

.method private hidebysig static int32 Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: call int32 Program::SomeNumber()
    L_0005: stloc.0 
    L_0006: leave.s L_000e
    L_0008: call void Program::Foo()
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e
}

यह मूल रूप से एक स्थानीय चर घोषित करता है ( CS$1$0000), मान को चर में रखता है (संभाला ब्लॉक के अंदर), फिर बाहर निकलने के बाद ब्लॉक चर को लोड करता है, फिर उसे वापस करता है। रिफ्लेक्टर इसे प्रस्तुत करता है:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}

10
क्या यह ठीक नहीं है, जिसे ओगेडेडियो ने कहा था: अंत में रिटर्न वैल्यू की गणना के बाद और वास्तव में कवक से लौटने से पहले निष्पादित किया जाता है ???
mmmmmmmm

"अपवाद-संभाला ब्लॉक" मुझे लगता है कि इस परिदृश्य का अपवाद और अपवाद से निपटने से कोई लेना-देना नहीं है। यह इस बारे में है कि .NET आखिर संसाधन रक्षक का निर्माण कैसे करता है।
g.pickardou

361

अंत में स्टेटमेंट निष्पादित किया जाता है, लेकिन रिटर्न वैल्यू प्रभावित नहीं होती है। निष्पादन आदेश है:

  1. रिटर्न स्टेटमेंट से पहले कोड निष्पादित किया जाता है
  2. रिटर्न स्टेटमेंट में अभिव्यक्ति का मूल्यांकन किया जाता है
  3. अंत में ब्लॉक निष्पादित किया जाता है
  4. चरण 2 में मूल्यांकन परिणाम वापस आ गया है

यहाँ एक छोटा कार्यक्रम दिखाया गया है:

using System;

class Test
{
    static string x;

    static void Main()
    {
        Console.WriteLine(Method());
        Console.WriteLine(x);
    }

    static string Method()
    {
        try
        {
            x = "try";
            return x;
        }
        finally
        {
            x = "finally";
        }
    }
}

यह प्रिंट "कोशिश" करता है (क्योंकि जो लौटा है) और फिर "अंत में" क्योंकि यह x का नया मूल्य है।

बेशक, अगर हम एक उत्परिवर्तित वस्तु (जैसे एक StringBuilder) के संदर्भ में लौट रहे हैं, तो अंत में ब्लॉक में ऑब्जेक्ट में किए गए कोई भी परिवर्तन रिटर्न पर दिखाई देंगे - इससे रिटर्न वैल्यू ही प्रभावित नहीं हुई है (जो सिर्फ एक है संदर्भ)।


मैं पूछना चाहता हूं कि क्या दृश्य स्टूडियो में कोई विकल्प है कि लिखित सी # कोड के लिए उत्पन्न इंटरमीडिएट भाषा (आईएल) को निष्पादन के समय देखा जाए ...
एनगमा स्टेट

"यदि हम एक उत्परिवर्तित वस्तु (जैसे एक StringBuilder) के संदर्भ में लौट रहे हैं, तो अंत में ब्लॉक में ऑब्जेक्ट में किए गए कोई भी परिवर्तन दिखाई देंगे" यदि StringBuilder ऑब्जेक्ट अंततः ब्लॉक में शून्य पर सेट है। जिस स्थिति में एक गैर-अशक्त वस्तु वापस आ जाती है।
निक

4
@ निक: यह वस्तु में परिवर्तन नहीं है - यह परिवर्तनशील परिवर्तन है । यह उस ऑब्जेक्ट को प्रभावित नहीं करता है जो चर के पिछले मूल्य को बिल्कुल भी संदर्भित करता है। तो नहीं, यह कोई अपवाद नहीं है।
जॉन स्कीट

3
@Skeet क्या इसका मतलब है "वापसी विवरण एक प्रति लौटाता है"?
प्रभाकरन

4
@ प्रभाकरन: वैसे यह returnकथन के बिंदु पर अभिव्यक्ति का मूल्यांकन करता है , और उस मूल्य को वापस कर दिया जाएगा। अभिव्यक्ति का मूल्यांकन नहीं किया जाता है क्योंकि नियंत्रण विधि को छोड़ देता है।
जॉन स्कीट

19

अंत में क्लॉज रिटर्न स्टेटमेंट के बाद निष्पादित होता है लेकिन वास्तव में फ़ंक्शन से लौटने से पहले। यह थ्रेड सुरक्षा के साथ बहुत कम है, मुझे लगता है। यह हैक नहीं है - अंत में हमेशा यह सुनिश्चित करने की गारंटी दी जाती है कि आप अपने ट्रायल ब्लॉक या कैच ब्लॉक में क्या करें।


13

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

"क्या" जो वापस लौटता है वह उसी प्रकार के तर्क का अनुसरण करता है जैसे:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        try {
            return ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
    }
}

जो संदर्भ लौटाया जा रहा है, उसका मूल्यांकन पहले ही किया जा चुका है, क्योंकि स्थानीय चर को अंत में ब्लॉक में एक नया संदर्भ सौंपा गया है।

निष्पादन अनिवार्य रूप से है:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        Exception CS$1$0000 = null;
        try {
            CS$1$0000 = ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
        return CS$1$0000;
    }
}

यह अंतर यह है कि वस्तु के गुणों / विधियों का उपयोग करके उत्परिवर्तित प्रकारों को संशोधित करना अभी भी संभव होगा जिसके परिणामस्वरूप अनपेक्षित व्यवहार हो सकते हैं यदि आप सावधान नहीं हैं।

class Test2 {
    public static System.IO.MemoryStream BadStream(byte[] buffer) {
        System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
        try {
            return ms;
        } finally {
            // Reference unchanged, Referenced Object changed
            ms.Dispose();
        }
    }
}

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

class ByRefTests {
    public static int One(out int i) {
        try {
            i = 1;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 1000;
        }
    }

    public static int Two(ref int i) {
        try {
            i = 2;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 2000;
        }
    }

    public static int Three(out int i) {
        try {
            return 3;
        } finally {
            // This is not a compile error!
            // Return value unchanged, Store new value referenced variable
            i = 3000;
        }
    }
}

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


4

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

मैं केवल ऐसा करते हुए देख सकता हूं यदि आप रिटर्न पर क्षेत्र के मूल्य के परिवर्तन की गारंटी देना चाहते हैं (और वापसी मूल्य निर्धारित होने के बाद)।


जब तक कि स्थानीय चर भी एक प्रतिनिधि द्वारा कब्जा नहीं किया जाता है :)
जॉन स्कीट

तब एक बंद होता है, और वस्तु को एकत्र नहीं किया जा सकता है, क्योंकि अभी भी एक संदर्भ है।
पुनरावर्ती

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