เหตุใดจึงไม่มีการประกาศตัวแปรใน“ ลอง” ในขอบเขตใน“ จับ” หรือ“ ในที่สุด”?


139

ใน C # และใน Java (และภาษาอื่น ๆ ด้วย) ตัวแปรที่ประกาศในบล็อก "ลอง" ไม่ได้อยู่ในขอบเขตในบล็อก "catch" หรือ "สุดท้าย" ที่สอดคล้องกัน ตัวอย่างเช่นรหัสต่อไปนี้ไม่ได้รวบรวม:

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

ในรหัสนี้ข้อผิดพลาดเวลาคอมไพล์เกิดขึ้นในการอ้างอิงถึง s ใน catch block เนื่องจาก s อยู่ในขอบเขตในลองบล็อกเท่านั้น (ใน Java ข้อผิดพลาดในการคอมไพล์คือ "s ไม่สามารถแก้ไขได้" ใน C # คือ "ชื่อ 'ไม่มีอยู่ในบริบทปัจจุบัน")

วิธีแก้ปัญหาทั่วไปของปัญหานี้ดูเหมือนว่าจะประกาศตัวแปรก่อนหน้าบล็อกลองแทนที่จะเป็นภายในบล็อกลอง:

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

อย่างไรก็ตามอย่างน้อยสำหรับฉัน (1) สิ่งนี้ให้ความรู้สึกเหมือนการแก้ปัญหาแบบ clunky และ (2) ส่งผลให้ตัวแปรมีขอบเขตที่ใหญ่กว่าโปรแกรมที่ตั้งใจไว้ (ส่วนที่เหลือทั้งหมดของวิธีแทนที่จะเป็นเฉพาะในบริบทของ ลองจับในที่สุด)

คำถามของฉันคืออะไร / เหตุผลใดที่อยู่เบื้องหลังการตัดสินใจออกแบบภาษานี้ (ใน Java, ใน C #, และ / หรือในภาษาอื่นที่เกี่ยวข้อง)

คำตอบ:


171

สองสิ่ง:

  1. โดยทั่วไป Java มีขอบเขตเพียง 2 ระดับ: ระดับโลกและฟังก์ชัน แต่ลอง / จับเป็นข้อยกเว้น (ไม่มีเล่นสำนวนเจตนา) เมื่อมีการโยนข้อยกเว้นและวัตถุการยกเว้นได้รับตัวแปรที่กำหนดให้ตัวแปรวัตถุนั้นจะใช้ได้เฉพาะในส่วน "catch" และจะถูกทำลายทันทีที่การจับเสร็จสมบูรณ์

  2. (และที่สำคัญกว่านั้น) คุณไม่สามารถรู้ได้ว่ามีข้อผิดพลาดในการลองบล็อกอยู่ที่ไหน อาจเป็นไปได้ก่อนที่จะประกาศตัวแปรของคุณ ดังนั้นจึงเป็นไปไม่ได้ที่จะบอกว่าตัวแปรใดจะมีให้สำหรับประโยค catch / ในที่สุด พิจารณากรณีต่อไปนี้โดยกำหนดขอบเขตตามที่คุณแนะนำ:

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {  
        Console.Out.WriteLine(s);
    }

นี่เป็นปัญหาอย่างชัดเจน - เมื่อคุณเข้าถึงตัวจัดการข้อยกเว้น s จะไม่ถูกประกาศ เนื่องจากการจับนั้นมีจุดประสงค์เพื่อจัดการกับสถานการณ์พิเศษและในที่สุดจะต้องดำเนินการความปลอดภัยและการประกาศปัญหานี้ในเวลาคอมไพล์นั้นดีกว่าตอนรันไทม์


55

คุณแน่ใจได้อย่างไรว่าถึงส่วนประกาศในบล็อก catch ของคุณ จะเกิดอะไรขึ้นถ้าการสร้างอินสแตนซ์เกิดข้อยกเว้น


6
ฮะ? การประกาศตัวแปรไม่ส่งข้อยกเว้น
Joshua

6
เห็นด้วยคือการสร้างอินสแตนซ์ที่อาจทำให้เกิดข้อยกเว้น
Burkhard

19

ตามเนื้อผ้าในภาษา C สไตล์สิ่งที่เกิดขึ้นภายในวงเล็บปีกกาหยิกอยู่ภายในวงเล็บปีกกา ฉันคิดว่าการที่อายุการใช้งานของตัวแปรยืดไปตามขอบเขตเช่นนั้นจะไม่สะดวกสำหรับโปรแกรมเมอร์ส่วนใหญ่ คุณสามารถบรรลุสิ่งที่คุณต้องการโดยการปิดกั้น try / catch / ในที่สุดภายในวงเล็บปีกกาอีกระดับ เช่น

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

แก้ไข: ฉันเดาว่ากฎทุกข้อจะมีข้อยกเว้น ต่อไปนี้คือ C ++ ที่ถูกต้อง:

int f() { return 0; }

void main() 
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

ขอบเขตของ x เป็นเงื่อนไขประโยคนั้นแล้วและประโยคอื่น


10

คนอื่น ๆ ได้นำพื้นฐานมา - สิ่งที่เกิดขึ้นในบล็อกยังคงอยู่ในบล็อก แต่ในกรณีของ. NET มันอาจจะเป็นประโยชน์ในการตรวจสอบสิ่งที่คอมไพเลอร์คิดว่าเกิดขึ้น รับตัวอย่างรหัสลอง / จับต่อไปนี้ (โปรดทราบว่า StreamReader ถูกประกาศอย่างถูกต้องนอกบล็อก):

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

สิ่งนี้จะรวบรวมสิ่งที่คล้ายกับสิ่งต่อไปนี้ใน MSIL:

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2    
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,    
           [1] class [mscorlib]System.Exception ex)    
  IL_0000:  ldnull    
  IL_0001:  stloc.0    
  .try    
  {    
    .try    
    {    
      IL_0002:  ldsfld     string UsingTest.Class1::path    
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)    
      IL_000c:  stloc.0    
      IL_000d:  ldloc.0    
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception 
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1    
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()    
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0026:  leave.s    IL_0028    
    }  // end handler    
    IL_0028:  leave.s    IL_0034    
  }  // end .try    
  finally    
  {    
    IL_002a:  ldloc.0    
    IL_002b:  brfalse.s  IL_0033    
    IL_002d:  ldloc.0    
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()    
    IL_0033:  endfinally    
  }  // end handler    
  IL_0034:  ret    
} // end of method Class1::TryCatchFinallyDispose

เราเห็นอะไร MSIL เคารพบล็อก - มันเป็นส่วนหนึ่งของรหัสพื้นฐานที่สร้างขึ้นเมื่อคุณคอมไพล์ C # ของคุณ ขอบเขตไม่เพียง แต่ตั้งค่าอย่างหนักในข้อมูลจำเพาะ C # แต่อยู่ในข้อมูลจำเพาะ CLR และ CLS เช่นกัน

ขอบเขตคุ้มครองคุณ แต่บางครั้งคุณต้องหลีกเลี่ยง เมื่อเวลาผ่านไปคุณจะชินกับมันและมันเริ่มรู้สึกเป็นธรรมชาติ อย่างที่ทุกคนพูดว่าสิ่งที่เกิดขึ้นในบล็อกนั้นอยู่ในบล็อกนั้น คุณต้องการแบ่งปันอะไร คุณต้องออกไปนอกตึก ...


8

ใน C ++ ที่อัตราใด ๆ ขอบเขตของตัวแปรอัตโนมัติจะถูก จำกัด ด้วยเครื่องหมายปีกกาที่ล้อมรอบ ทำไมทุกคนคาดหวังว่าสิ่งนี้จะแตกต่างกันโดยการลองทำคำค้นหาลองนอกวงเล็บปีกกา


1
ตกลงกัน; "}" หมายถึงจุดสิ้นสุดขอบเขต อย่างไรก็ตามลองจับในที่สุดก็ผิดปกติในที่หลังจากลองบล็อกคุณจะต้องมีการจับและ / หรือในที่สุดบล็อก; ดังนั้นข้อยกเว้นสำหรับกฎปกติที่ขอบเขตของบล็อกลองนำไปสู่การจับที่เกี่ยวข้อง / ในที่สุดอาจดูเหมือนยอมรับได้หรือไม่
Jon Schneider

7

ทุกคนคาดหวังว่าตัวแปรจะเป็นแบบโลคัลสำหรับบล็อกที่กำหนดไว้tryแนะนำบล็อกและสิ่งอื่นcatch

หากคุณต้องการให้ตัวแปรในตัวเครื่องมีทั้งคู่tryและcatchให้ลองใส่ทั้งสองตัวในบล็อก:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}

5

คำตอบง่ายๆคือ C และภาษาส่วนใหญ่ที่ได้รับมาจากไวยากรณ์นั้นจะถูกกำหนดขอบเขตแบบบล็อก นั่นหมายความว่าหากมีการกำหนดตัวแปรในหนึ่งบล็อกเช่นภายใน {} นั่นคือขอบเขตของมัน

โดยข้อยกเว้นคือ JavaScript ซึ่งมีไวยากรณ์ที่คล้ายกัน แต่มีการกำหนดขอบเขตของฟังก์ชัน ใน JavaScript ตัวแปรที่ประกาศในบล็อกลองอยู่ในขอบเขตใน catch block และทุกที่อื่นในฟังก์ชันที่มี


4

@ Burkhard มีคำถามว่าทำไมตอบอย่างถูกต้อง แต่เป็นบันทึกที่ฉันต้องการเพิ่มในขณะที่ตัวอย่างโซลูชันที่แนะนำของคุณดี 99.9999 +% เวลามันไม่ใช่วิธีปฏิบัติที่ดีมันปลอดภัยกว่าที่จะตรวจสอบ null ก่อนใช้งาน สิ่งที่อินสแตนซ์ภายในบล็อกลองหรือเริ่มต้นตัวแปรเป็นสิ่งที่แทนที่จะประกาศเพียงก่อนลองบล็อก ตัวอย่างเช่น:

string s = String.Empty;
try
{
    //do work
}
catch
{
   //safely access s
   Console.WriteLine(s);
}

หรือ:

string s;
try
{
    //do work
}
catch
{
   if (!String.IsNullOrEmpty(s))
   {
       //safely access s
       Console.WriteLine(s);
   }
}

สิ่งนี้ควรให้ความยืดหยุ่นในการแก้ปัญหาดังนั้นแม้ว่าสิ่งที่คุณกำลังทำในลองบล็อกนั้นซับซ้อนกว่าการกำหนดสตริงคุณควรสามารถเข้าถึงข้อมูลได้อย่างปลอดภัยจาก catch block ของคุณ


4

ตามส่วนที่ชื่อว่า "วิธีการโยนและจับข้อยกเว้น" ในบทที่ 2 ของชุดการฝึกอบรม Self-Paced MCTS (ตรวจสอบ 70-536): Microsoft® .NET Framework 2.0 - รากฐานการพัฒนาแอปพลิเคชันเหตุผลคืออาจมีข้อยกเว้นเกิดขึ้น ก่อนการประกาศตัวแปรในบล็อกลอง (ตามที่คนอื่น ๆ ได้ระบุไว้แล้ว)

อ้างอิงจากหน้า 25:

"ขอให้สังเกตว่าการประกาศ StreamReader ถูกย้ายนอกบล็อกลองในตัวอย่างก่อนหน้านี้เป็นสิ่งจำเป็นเพราะบล็อกสุดท้ายไม่สามารถเข้าถึงตัวแปรที่ประกาศไว้ในบล็อกทดลองได้สิ่งนี้สมเหตุสมผลเนื่องจากขึ้นอยู่กับข้อยกเว้นที่เกิดขึ้นการประกาศตัวแปรภายใน ลองบล็อกอาจยังไม่ได้ดำเนินการ "


4

คำตอบที่ทุกคนได้ชี้ให้เห็นนั้นค่อนข้างมาก "นั่นเป็นวิธีกำหนดบล็อก"

มีข้อเสนอบางอย่างที่จะทำให้โค้ดสวยขึ้น ดูARM

 try (FileReader in = makeReader(), FileWriter out = makeWriter()) {
       // code using in and out
 } catch(IOException e) {
       // ...
 }

การปิดบัญชีก็ควรจะแก้ไขปัญหานี้เช่นกัน

with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) {
    // code using in and out
}

อัปเดต: ARM มีการใช้งานใน Java 7 http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html


2

วิธีแก้ปัญหาคือสิ่งที่คุณควรทำ คุณไม่สามารถแน่ใจได้ว่าถึงการประกาศของคุณแม้ในบล็อกลองซึ่งจะส่งผลให้เกิดข้อยกเว้นอื่นในบล็อก catch

มันจะต้องทำงานเป็นขอบเขตที่แยกจากกัน

try
    dim i as integer = 10 / 0 ''// Throw an exception
    dim s as string = "hi"
catch (e)
    console.writeln(s) ''// Would throw another exception, if this was allowed to compile
end try

2

ตัวแปรคือระดับบล็อกและถูก จำกัด ให้ลองหรือจับบล็อก คล้ายกับการกำหนดตัวแปรในคำสั่ง if คิดว่าสถานการณ์นี้

try {    
    fileOpen("no real file Name");    
    String s = "GO TROJANS"; 
} catch (Exception) {   
    print(s); 
}

สตริงจะไม่ถูกประกาศดังนั้นจึงไม่สามารถขึ้นอยู่กับ


2

เพราะบล็อกลองและบล็อกจับเป็นบล็อกที่แตกต่างกัน 2 แบบ

ในรหัสต่อไปนี้คุณคาดว่าจะมีการกำหนดไว้ในบล็อก A ในบล็อก B หรือไม่

{ // block A
  string s = "dude";
}

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}

2

ในตัวอย่างของคุณมันแปลกที่มันใช้งานไม่ได้ลองทำสิ่งที่คล้ายกันนี้:

    try
    {
         //Code 1
         String s = "1|2";
         //Code 2
    }
    catch
    {
         Console.WriteLine(s.Split('|')[1]);
    }

สิ่งนี้จะทำให้การดักจับโยนข้อยกเว้นอ้างอิงเป็นค่าว่างหากรหัส 1 หัก ตอนนี้ในขณะที่ความหมายของการพยายามจับ / เข้าใจค่อนข้างจะเป็นกรณีมุมที่น่ารำคาญเนื่องจาก s ถูกกำหนดด้วยค่าเริ่มต้นดังนั้นในทางทฤษฎีควรจะไม่เป็นโมฆะ แต่ภายใต้ความหมายที่ใช้ร่วมกันก็จะเป็น

อีกครั้งในทางทฤษฎีอาจได้รับการแก้ไขโดยอนุญาตให้แยกนิยาม ( String s; s = "1|2";) หรือเงื่อนไขอื่น ๆ บางอย่างเท่านั้น แต่โดยทั่วไปแล้วจะง่ายกว่าที่จะบอกว่าไม่

นอกจากนี้จะช่วยให้ความหมายของขอบเขตที่จะกำหนดทั่วโลกโดยไม่มีข้อยกเว้นโดยเฉพาะอย่างยิ่งชาวท้องถิ่นล่าสุดตราบเท่าที่{}พวกเขาจะถูกกำหนดไว้ในทุกกรณี จุดรอง แต่เป็นจุด

สุดท้ายเพื่อทำสิ่งที่คุณต้องการคุณสามารถเพิ่มชุดของวงเล็บรอบลองจับ ให้ขอบเขตที่คุณต้องการถึงแม้ว่ามันจะมีค่าใช้จ่ายในการอ่านน้อย แต่ก็ไม่มากเกินไป

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}

1

ในตัวอย่างที่คุณระบุไว้การกำหนดค่าเริ่มต้น s ไม่สามารถเกิดข้อยกเว้นได้ ดังนั้นคุณคิดว่าอาจขยายขอบเขตได้

แต่โดยทั่วไปนิพจน์เริ่มต้นสามารถส่งข้อยกเว้นได้ มันจะไม่สมเหตุสมผลสำหรับตัวแปรที่ initialiser ส่งข้อยกเว้น (หรือซึ่งถูกประกาศหลังจากตัวแปรอื่นที่เกิดขึ้น) ให้อยู่ในขอบเขตของการตรวจจับ / ในที่สุด

นอกจากนี้การอ่านรหัสจะประสบ กฎใน C (และภาษาที่ตามมารวมถึง C ++, Java และ C #) นั้นง่าย: ขอบเขตของตัวแปรติดตามบล็อก

หากคุณต้องการให้ตัวแปรอยู่ในขอบเขตสำหรับลอง / จับ / ในที่สุด แต่ไม่มีที่อื่นให้ห่อทุกอย่างในวงเล็บอีกชุดหนึ่ง (บล็อกเปลือย) และประกาศตัวแปรก่อนลอง


1

ส่วนหนึ่งของเหตุผลที่พวกเขาไม่อยู่ในขอบเขตเดียวกันนั้นเป็นเพราะ ณ จุดใด ๆ ของลองบล็อกคุณสามารถโยนข้อยกเว้น หากพวกเขาอยู่ในขอบเขตเดียวกันมันเป็นหายนะในการรอเพราะขึ้นอยู่กับข้อยกเว้นที่ถูกโยนมันอาจจะคลุมเครือมากขึ้น

อย่างน้อยเมื่อมีการประกาศนอกบล็อกลองคุณรู้ว่าตัวแปรอย่างน้อยอาจเป็นเมื่อมีข้อยกเว้นจะถูกโยน; ค่าของตัวแปรก่อนหน้าบล็อกลอง


1

เมื่อคุณประกาศตัวแปรท้องถิ่นมันจะถูกวางไว้บนสแต็ค (สำหรับบางประเภทค่าทั้งหมดของวัตถุจะอยู่ในสแต็กสำหรับประเภทอื่น ๆ เท่านั้นการอ้างอิงจะอยู่ในสแต็ก) เมื่อมีข้อยกเว้นภายในบล็อกลองตัวแปรท้องถิ่นภายในบล็อกนั้นเป็นอิสระซึ่งหมายความว่าสแต็กจะ "คลาย" กลับสู่สถานะเดิมเมื่อเริ่มต้นบล็อกทดลอง นี่คือโดยการออกแบบ เป็นวิธีที่ try / catch สามารถสำรองการเรียกฟังก์ชันทั้งหมดภายในบล็อกและทำให้ระบบของคุณกลับสู่สถานะใช้งานได้ หากไม่มีกลไกนี้คุณจะไม่สามารถมั่นใจได้ว่ามีสิ่งใดเกิดขึ้นเมื่อมีข้อยกเว้นเกิดขึ้น

การมีรหัสการจัดการข้อผิดพลาดของคุณขึ้นอยู่กับตัวแปรที่ประกาศจากภายนอกซึ่งมีการเปลี่ยนแปลงค่าภายในบล็อกลองดูเหมือนว่าการออกแบบที่ไม่ดีสำหรับฉัน สิ่งที่คุณกำลังทำคือการรั่วไหลของทรัพยากรโดยเจตนาเพื่อรับข้อมูล (ในกรณีนี้มันไม่เลวร้ายนักเพราะคุณเป็นเพียงแค่การรั่วไหลของข้อมูล แต่คิดว่ามันเป็นทรัพยากรอื่น ๆ หรือเปล่า? อนาคต). ฉันขอแนะนำให้แบ่งบล็อกลองเป็นชิ้นเล็ก ๆ ถ้าคุณต้องการความละเอียดมากขึ้นในการจัดการข้อผิดพลาด


1

เมื่อคุณลองจับคุณส่วนใหญ่ควรรู้ว่าข้อผิดพลาดที่อาจเกิดขึ้น ปกติคลาส Theese Exception จะบอกทุกสิ่งที่คุณต้องการเกี่ยวกับข้อยกเว้น ถ้าไม่คุณควรทำให้คุณเป็นคลาสยกเว้นและส่งผ่านข้อมูลนั้นไป ด้วยวิธีนี้คุณจะไม่ต้องรับตัวแปรจากภายในบล็อกลองเพราะข้อยกเว้นเป็นการอธิบายตนเอง ดังนั้นหากคุณต้องการทำสิ่งนี้มาก ๆ ลองคิดถึงการออกแบบและลองคิดดูว่ามีวิธีอื่นไหมที่คุณสามารถคาดการณ์ข้อยกเว้นที่มาหรือใช้ข้อมูลที่มาจากข้อยกเว้นแล้วค่อยทำใหม่อีกครั้ง ข้อยกเว้นพร้อมข้อมูลเพิ่มเติม


1

ดังที่ผู้ใช้คนอื่น ๆ ชี้ให้เห็นวงเล็บปีกกากำหนดขอบเขตในภาษา C ทุกสไตล์ที่ฉันรู้จัก

ถ้ามันเป็นตัวแปรอย่างง่ายทำไมคุณถึงสนใจว่ามันจะอยู่ในขอบเขตนานแค่ไหน? มันไม่ได้เป็นเรื่องใหญ่

ใน C # ถ้ามันเป็นตัวแปรที่ซับซ้อนคุณจะต้องใช้ IDisposable จากนั้นคุณสามารถใช้ลอง / จับ / ในที่สุดและเรียกใช้ obj.Dispose () ในบล็อกสุดท้าย หรือคุณสามารถใช้คำหลักที่ใช้ซึ่งจะเรียกใช้งานการกำจัดโดยอัตโนมัติที่ส่วนท้ายของรหัส


1

ในงูหลามพวกเขาจะปรากฏในจับ / ในที่สุดบล็อกถ้าบรรทัดที่ประกาศพวกเขาไม่ได้โยน


1

จะเกิดอะไรขึ้นถ้ามีข้อยกเว้นเกิดขึ้นในบางโค้ดซึ่งอยู่เหนือการประกาศของตัวแปร ซึ่งหมายความว่าการประกาศตัวเองไม่ได้เกิดขึ้นในกรณีนี้

try {

       //doSomeWork // Exception is thrown in this line. 
       String s;
       //doRestOfTheWork

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}

1

C # Spec (15.2) ระบุว่า "ขอบเขตของตัวแปรท้องถิ่นหรือคงที่ประกาศในบล็อก ist บล็อก."

(ในตัวอย่างแรกของคุณบล็อกลองคือบล็อกที่มีการประกาศ "s")


0

ความคิดของฉันน่าจะเป็นเพราะบางสิ่งในบล็อกลองเรียกใช้ข้อยกเว้นว่าเนื้อหาเนมสเปซไม่สามารถเชื่อถือได้ - เช่นการอ้างอิงสตริงของ '' ในบล็อก catch อาจทำให้เกิดข้อยกเว้นอีกประการหนึ่ง


0

ถ้ามันไม่ได้เกิดข้อผิดพลาดในการคอมไพล์และคุณสามารถประกาศมันสำหรับส่วนที่เหลือของวิธีการนั้นจะไม่มีทางที่จะประกาศเพียงภายในขอบเขตลอง มันบังคับให้คุณต้องมีความชัดเจนว่าตัวแปรนั้นควรมีอยู่ที่ไหนและไม่ได้ตั้งสมมติฐาน


0

หากเราเพิกเฉยต่อปัญหาการบล็อกขอบเขตสักครู่หนึ่งผู้รวบรวมจะต้องทำงานหนักขึ้นในสถานการณ์ที่ไม่ได้กำหนดไว้อย่างดี ในขณะที่สิ่งนี้เป็นไปไม่ได้ข้อผิดพลาดในการกำหนดขอบเขตยังบังคับให้คุณซึ่งเป็นผู้เขียนโค้ดตระหนักถึงความเกี่ยวข้องของโค้ดที่คุณเขียน (ซึ่งสตริงนั้นอาจเป็นโมฆะในบล็อก catch) หากรหัสของคุณถูกกฎหมายในกรณีของข้อยกเว้น OutOfMemory s จะไม่รับประกันว่าจะได้รับการจัดสรรช่องเสียบหน่วยความจำ:

// won't compile!
try
{
    VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
    string s = "Help";
}
catch
{
    Console.WriteLine(s); // whoops!
}

CLR (และคอมไพเลอร์) จึงบังคับให้คุณเริ่มต้นตัวแปรก่อนที่จะใช้ ใน catch catch ที่นำเสนอมันไม่สามารถรับประกันได้

ดังนั้นเราจึงจบลงด้วยคอมไพเลอร์ที่ต้องทำงานมากมายซึ่งในทางปฏิบัติไม่ได้ผลประโยชน์มากนักและอาจทำให้ผู้คนสับสนและนำพวกเขาไปถามว่าทำไมลอง / จับงานแตกต่างกัน

นอกเหนือจากความสอดคล้องโดยไม่อนุญาตให้มีสิ่งใดแฟนซีและยึดมั่นในความหมายที่กำหนดไว้แล้วที่ใช้ไปแล้วทั่วทั้งภาษาคอมไพเลอร์และ CLR สามารถให้การรับประกันที่มากขึ้นเกี่ยวกับสถานะของตัวแปรในบล็อก catch ว่ามันมีอยู่และได้รับการเริ่มต้น

โปรดทราบว่านักออกแบบภาษาทำงานได้ดีกับสิ่งก่อสร้างอื่น ๆ เช่นการใช้งานและการล็อกซึ่งปัญหาและขอบเขตถูกกำหนดไว้อย่างดีซึ่งช่วยให้คุณสามารถเขียนรหัสที่ชัดเจนยิ่งขึ้น

เช่นคำหลักที่ใช้กับวัตถุIDisposableใน:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

เทียบเท่ากับ:

Writer writer = new Writer();
try
{        
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

หากการลอง / จับ / ในที่สุดยากที่จะเข้าใจลอง refactoring หรือแนะนำเลเยอร์ทางอ้อมอื่นด้วยคลาสกลางที่สรุปความหมายของสิ่งที่คุณพยายามทำ การไม่เห็นรหัสจริงเป็นการยากที่จะเจาะจงมากขึ้น


0

แทนที่จะเป็นตัวแปรโลคัลสามารถประกาศคุณสมบัติพับลิกได้ สิ่งนี้ควรหลีกเลี่ยงข้อผิดพลาดอื่นที่อาจเกิดขึ้นของตัวแปรที่ไม่ได้กำหนด ประชาชนสตริง S {รับ; ตั้ง; }


-1

หากการดำเนินการมอบหมายล้มเหลวข้อความสั่ง catch ของคุณจะมีการอ้างอิงเป็นโมฆะกลับไปยังตัวแปรที่ไม่ได้กำหนด


2
มันไม่ได้กำหนด มันไม่ได้เป็นโมฆะ (ต่างจากอินสแตนซ์และตัวแปรคงที่)
Tom Hawtin - tackline

-1

C # 3.0:

string html = new Func<string>(() =>
{
    string webpage;

    try
    {
        using(WebClient downloader = new WebClient())
        {
            webpage = downloader.DownloadString(url);
        }
    }
    catch(WebException)
    {
        Console.WriteLine("Download failed.");  
    }

    return webpage;
})();

WTF? ทำไมต้องโหวต การห่อหุ้มนั้นเป็นส่วนสำคัญของ OOP ดูน่ารักเหมือนกัน
แกน

2
ฉันไม่ใช่คนลงคะแนน แต่สิ่งที่ผิดคือส่งคืนสตริงที่ไม่มีการกำหนดค่าเริ่มต้น
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.