Neden “yakala” ya da “nihayet” te “try” kapsamında değişkenler bildirilmiyor?


139

C # ve Java'da (ve muhtemelen diğer dillerde de), "try" bloğunda bildirilen değişkenler, karşılık gelen "catch" veya "nihayet" bloklarda kapsam dahilinde değildir. Örneğin, aşağıdaki kod derlenmez:

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

Bu kodda, catch bloğundaki s referansında derleme zamanı hatası oluşur, çünkü s sadece try bloğunun kapsamı içindedir. (Java'da, derleme hatası "çözülemez"; C # 'da, "geçerli bağlamda' s 'adı yok".)

Bu sorunun genel çözümü, try bloğunun yerine, try bloğundan hemen önce değişkenleri bildirmek gibi görünüyor:

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

Ancak, en azından bana göre, (1) bu tıknaz bir çözüm gibi hissediyor ve (2) değişkenlerin, programlayıcının amaçladığından daha geniş bir kapsama sahip olmasıyla sonuçlanıyor (yalnızca yöntemin bağlamı yerine, yöntemin geri kalanının tamamı) deneyin-catch-son).

Benim sorum, bu dil tasarım kararının arkasındaki gerekçeler nelerdi (Java, C # ve / veya diğer herhangi bir dilde)?

Yanıtlar:


171

İki şey:

  1. Genel olarak Java'nın yalnızca 2 kapsam düzeyi vardır: genel ve işlev. Ancak, try / catch bir istisnadır (cinas amaçlanmamıştır). Bir istisna atılır ve istisna nesnesi kendisine atanan bir değişken alırsa, bu nesne değişkeni yalnızca "catch" bölümünde kullanılabilir ve catch tamamlanır tamamlanmaz yok edilir.

  2. (ve daha da önemlisi). Deneme bloğunda istisnanın nereye atıldığını bilemezsiniz. Değişkeniniz bildirilmeden önce olmuş olabilir. Bu nedenle catch / nihayet cümlesi için hangi değişkenlerin mevcut olacağını söylemek mümkün değildir. Kapsam belirlemenin önerildiği gibi olduğu aşağıdaki durumu düşünün:

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

Bu açıkça bir sorundur - istisna işleyiciye ulaştığınızda, s bildirilmez. Yakalamaların istisnai durumlarla başa çıkması ve nihayetinde yürütülmesi gerektiği göz önüne alındığında, güvenli olmak ve bunu derleme zamanında bir sorun olarak bildirmek çalışma süresinden çok daha iyidir.


55

Yakalama bloğunuzdaki bildirim bölümüne ulaştığınızdan nasıl emin olabilirsiniz? Ya örnekleme istisnayı atarsa?


6
Ha? Değişken bildirimler istisna oluşturmaz.
Joshua

6
Kabul etti, istisnayı atabilecek örnek.
Burkhard

19

Geleneksel olarak, C tarzı dillerde, kıvırcık ayraçların içinde olan şey kıvırcık ayraçların içinde kalır. Bunun gibi kapsamlar arasında değişken bir esneme ömrüne sahip olmanın çoğu programcı için sezgisel olmayacağını düşünüyorum. Try / catch / nihayet blokları başka bir parantez seviyesi içine alarak istediğinizi elde edebilirsiniz. Örneğin

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

DÜZENLEME: Her kural tahmin etmez bir istisna var. Aşağıdaki geçerli C ++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

X'in kapsamı koşullu, o zaman yan tümcesi ve başka yan tümcedir.


10

Diğer herkes temelleri getirdi - bir blokta olanlar bir blokta kalır. Ancak .NET durumunda, derleyicinin ne düşündüğünü incelemek yararlı olabilir. Örneğin, aşağıdaki try / catch kodunu ele alalım (StreamReader'ın blokların dışında doğru bir şekilde bildirildiğine dikkat edin):

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();
        }
    }
}

Bu, MSIL'de aşağıdakine benzer bir şey derleyecektir:

.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

Ne görüyoruz? MSIL bloklara saygı duyar - bunlar C # 'nizi derlediğinizde oluşturulan temel kodun bir parçasıdır. Kapsam sadece C # spec'te sabit değil, CLR ve CLS spesifikasyonlarında da.

Kapsam sizi korur, ancak bazen etrafınızda çalışmak zorunda kalırsınız. Zamanla, buna alışırsınız ve doğal hissetmeye başlar. Herkesin dediği gibi, bir blokta neler olduğu o blokta kalır. Bir şey paylaşmak ister misin? Blokların dışına çıkmalısın ...


8

Herhangi bir oranda C ++ 'da, otomatik bir değişkenin kapsamı, onu çevreleyen kıvırcık parantezlerle sınırlıdır. Neden herkes kıvırcık parantez dışında bir try anahtar kelimesini daraltarak bunun farklı olmasını beklesin ki?


1
Kabul; "}" kapsam sonu anlamına gelir. Ancak, try-catch-nihayet bir try bloğundan sonra bir catch ve / veya nihayet blokajınız olması olağandışıdır ; böylece, ilişkili catch / nihayet taşınan bir try bloğunun kapsamı kabul edilebilir görünebilir normal kural bir istisna?
Jon Schneider

7

Ravenspoint'in işaret ettiği gibi, herkes değişkenlerin tanımlandıkları blokta yerel olmasını bekler. tryBir blok getirir ve öyle yapar catch.

Değişkenlerin ikisine de yerel olmasını istiyorsanız tryve catchher ikisini de bir bloğa eklemeyi deneyin:

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

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

5

Basit cevap, C ve sözdizimini miras alan dillerin çoğunun blok kapsamıdır. Bu, eğer bir değişken bir blokta tanımlanırsa, yani {} içinde onun kapsamı olduğu anlamına gelir.

Bu arada, istisna benzer bir sözdizimine sahip, ancak işlev kapsamı olan JavaScript'tir. JavaScript'te, try bloğunda bildirilen bir değişken catch bloğunda ve içerme işlevindeki diğer her yerde kapsam dahilindedir.


4

@burkhard neden doğru cevaplandığı sorusuna sahiptir, ancak eklemek istediğim bir not olarak, önerilen çözüm örneğiniz iyi 99.9999 +% zaman olsa da, iyi bir uygulama değildir, kullanmadan önce null olup olmadığını kontrol etmek çok daha güvenlidir try bloğunda bir şey başlatır veya değişkeni try bloğundan önce bildirmek yerine bir şeye başlatır. Örneğin:

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

Veya:

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

Bu, geçici çözümde ölçeklenebilirlik sağlamalıdır, böylece try bloğunda yaptığınız şey bir dize atamaktan daha karmaşık olsa bile, catch bloğunuzdaki verilere güvenli bir şekilde erişebilmeniz gerekir.


4

MCTS Kendi Hızınızda Eğitim Seti (Sınav 70-536) Ders 2'deki "İstisnalar Nasıl Atılır ve Yakalanır" başlıklı bölüme göre : Microsoft® .NET Framework 2.0 — Uygulama Geliştirme Vakfı'nın nedeni istisnanın oluşmuş olabileceğidir try bloğundaki değişken bildirimlerden önce (diğerleri zaten belirttiği gibi)

Sayfa 25'ten alıntı:

"Önceki örnekte StreamReader bildiriminin Try bloğunun dışına taşındığına dikkat edin. Bu, Nihayet bloğunun Try bloğu içinde bildirilen değişkenlere erişememesi nedeniyle gereklidir. Bu, bir istisnanın gerçekleştiği yere bağlı olarak, Deneme engellemesi henüz yürütülmemiş olabilir . "


4

Yanıt, herkesin işaret ettiği gibi, hemen hemen "bloklar bu şekilde tanımlanır".

Kodu daha güzel hale getirmek için bazı öneriler var. Bkz ARM

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

Kapanışların da buna değinmesi gerekiyor.

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

GÜNCELLEME: ARM Java 7'de uygulanır. Http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html


2

Çözümünüz tam olarak yapmanız gereken şeydir. Bildirinize try bloğunda bile ulaşıldığından emin olamazsınız, bu da catch bloğunda başka bir istisnaya neden olur.

Sadece ayrı kapsamlar olarak çalışmalıdır.

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

Değişkenler blok düzeyindedir ve Try veya Catch blokuyla sınırlıdır. Bir if ifadesinde bir değişken tanımlamaya benzer. Bu durumu düşünün.

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

Dize asla bildirilmez, bu nedenle ona bağlı olamaz.


2

Çünkü try bloğu ve catch bloğu 2 farklı bloktur.

Aşağıdaki kodda, A bloğunda tanımlanan s'nin B bloğunda görünür olmasını bekler misiniz?

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

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

2

Örneğinizde işe yaramaması garip olsa da, bunu benzer şekilde alın:

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

Bu, Kod 1'in kırılması durumunda yakalamanın boş bir başvuru istisnası atmasına neden olur. Şimdi, dene / yakala anlambilimi oldukça iyi anlaşılırken, bu sinir bozucu bir köşe vakası olacaktır, çünkü s başlangıç ​​değeri ile tanımlanmıştır, bu yüzden teoride asla boş olmamalıdır, ancak paylaşılan anlambilim altında, öyle olacaktır.

Yine, bu teoride sadece ayrılmış tanımlara ( String s; s = "1|2";) veya başka bir dizi koşula izin verilerek düzeltilebilir , ancak genellikle hayır demek daha kolaydır.

Buna ek olarak, kapsam anlambiliminin istisnasız global olarak tanımlanmasına izin verir, özellikle yerel halk {}, her durumda tanımlandıkları sürece devam eder . Küçük bir nokta, ama bir nokta.

Son olarak, istediğinizi yapmak için, try catch'in etrafına bir dizi parantez ekleyebilirsiniz. İstediğiniz kapsamı sağlar, ancak biraz okunabilirlik pahasına olsa da, çok fazla değildir.

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

1

Verdiğiniz belirli örnekte, başlatma işlemleri bir istisna alamaz. Yani kapsamının genişletilebileceğini düşünürsünüz.

Ancak genel olarak, başlatıcı ifadeleri istisnalar atabilir. Başlatan bir istisna atan (veya bunun gerçekleştiği başka bir değişkenin ardından bildirilen) bir değişkenin catch / nihayet kapsamında olması anlamlı olmaz.

Ayrıca, kod okunabilirliği zarar görür. C'deki kural (ve C ++, Java ve C # dahil olmak üzere onu takip eden diller) basittir: değişken kapsamları blokları takip eder.

Bir değişkenin try / catch / nihayetinde ancak başka hiçbir yerde kapsamda olmasını istiyorsanız, her şeyi başka bir küme parantezine (çıplak bir blok) sarın ve denemeden önce değişkeni bildirin.


1

Aynı kapsamda olmamalarının bir nedeni, try bloğunun herhangi bir noktasında istisnayı atmış olmanızdır. Aynı kapsamda olsaydı, beklemede bir felaket, çünkü istisnanın nereye atıldığına bağlı olarak, daha da belirsiz olabilir.

En azından try bloğunun dışında bildirildiğinde, bir istisna atıldığında değişkenin minimumda ne olabileceğinden emin olabilirsiniz; Try bloğundan önceki değişkenin değeri.


1

Yerel bir değişkeni yığına yerleştirdiğinizi bildirdiğinizde (bazı türler için nesnenin tüm değeri yığının üzerinde olur, diğer türler için yığının üzerinde yalnızca bir başvuru olur). Bir try bloğunun içinde bir istisna olduğunda, blok içindeki yerel değişkenler serbest bırakılır, bu da yığının try bloğunun başında olduğu duruma geri "açılması" anlamına gelir. Bu tasarım gereğidir. Try / catch, blok içindeki tüm fonksiyon çağrılarından nasıl geri çekilebilir ve sisteminizi tekrar işlevsel bir duruma sokar. Bu mekanizma olmadan, bir istisna oluştuğunda hiçbir şeyin durumundan asla emin olamazsınız.

Hata işleme kodunuzun değerlerini try bloğunda değiştiren harici olarak bildirilen değişkenlere güvenmek benim için kötü bir tasarım gibi görünüyor. Yaptığınız şey aslında bilgi elde etmek için kasıtlı olarak kaynak sızdırıyor (bu özel durumda o kadar da kötü değil çünkü sadece bilgi sızdırıyorsunuz, ancak bunun başka bir kaynak olup olmadığını hayal edin? gelecek). Hata işlemede daha fazla ayrıntıya ihtiyacınız varsa, deneme bloklarınızı daha küçük parçalara ayırmanızı öneririm.


1

Bir deneme yakalamanız olduğunda, çoğunlukla hataların atılabileceğini bilmelisiniz. Theese Exception sınıfları normalde istisna hakkında ihtiyacınız olan her şeyi anlatır. Değilse, kendi istisna sınıflarınızı oluşturmalı ve bu bilgileri aktarmalısınız. Bu şekilde, değişkenleri asla try bloğunun içinden almanız gerekmeyecektir, çünkü İstisna kendini açıklar. Yani bunu çok yapmanız gerekiyorsa, tasarımınızı düşünün ve başka bir yol olup olmadığını düşünmeye çalışın, istisnaların geleceğini tahmin edebileceğinizi veya istisnalardan gelen bilgileri kullanabileceğinizi ve belki de kendinizinkini yeniden oluşturabileceğinizi düşünün. daha fazla bilgi ile istisna.


1

Diğer kullanıcılar tarafından işaret edildiği gibi, kıvırcık parantezler kapsamı bildiğim hemen hemen her C stili dilinde tanımlar.

Basit bir değişkense, neden kapsamın ne kadar süreceğini umursuyorsunuz? O kadar büyük bir anlaşma değil.

C # 'da, karmaşık bir değişkense, IDisposable uygulamak isteyeceksiniz. Daha sonra try / catch / nihayet kullanabilir ve nihayet bloğunda obj.Dispose () öğesini çağırabilirsiniz. Veya kod bölümünün sonunda otomatik olarak Atma işlevini çağıran using anahtar sözcüğünü kullanabilirsiniz.


1

Python'da, onları bildiren satır fırlatmadıysa catch / nihayet bloklarında görünürler.


1

İstisna, değişkenin bildiriminin üzerindeki bazı kodlarda atılırsa. Yani, bildirgenin kendisi bu durumda gerçekleşmedi.

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) "Bir yerel değişken veya sabit kapsamı bloğunda ist bir blokta ilan etti." Devletler

(ilk örneğinizde try bloğu "s" ifadesinin bulunduğu bloktur)


0

Benim düşüncem, try bloğundaki bir şey istisnayı tetiklediğinden, ad alanı içeriğine güvenilemeyeceğinden, yani catch bloğundaki String 'lere atıfta bulunmak, başka bir istisnanın atılmasına neden olabilir.


0

Derleme hatası atmazsa ve yöntemin geri kalanı için bildirebilirseniz, yalnızca deneme kapsamında bildirmenin bir yolu olmazdı. Bu, değişkenin nerede olması gerektiği konusunda açık olmaya zorlar ve varsayımlar yapmaz.


0

Kapsam belirleme sorununu bir an için görmezden gelirsek, suç ortağının iyi tanımlanmamış bir durumda çok daha fazla çalışması gerekir. Bu imkansız olmasa da, kapsam belirleme hatası, kodun yazarı olarak, yazdığınız kodun (string dizesinin catch bloğunda boş olabileceğini) anlamını anlamaya zorlar. Kodunuz yasalsa, bir OutOfMemory istisnası durumunda, s'nin bir bellek yuvası tahsis edileceği bile garanti edilmez:

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

CLR (ve dolayısıyla derleyici) değişkenleri kullanılmadan önce başlatmaya da zorlar. Sunulan catch bloğunda bunu garanti edemez.

Böylece derleyicinin pratikte fazla fayda sağlamadığı ve muhtemelen insanları karıştırıp onları neden denemenin / yakalamanın farklı çalıştığını sormasına yol açacak çok fazla iş yapmak zorunda kalıyoruz.

Tutarlılığa ek olarak, süslü bir şeye izin vermemek ve dil boyunca kullanılan önceden belirlenmiş kapsam belirleme semantiklerine bağlı kalarak, derleyici ve CLR, bir catch bloğu içindeki bir değişkenin durumu için daha büyük bir garanti sağlayabilir. Var olduğu ve başlatıldığı.

Dil tasarımcılarının , sorunun ve kapsamın iyi tanımlandığı yerlerde kullanmak ve kilitlemek gibi diğer yapılarla iyi bir iş çıkardığını unutmayın; bu da daha net kod yazmanıza olanak tanır.

örneğin kullanarak olan anahtar kelimenin IDisposal nesneleri:

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

şuna eşittir:

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

Deneme / yakalama / son olarak anlaşılması zorsa, gerçekleştirmeye çalıştığınız şeyin anlambilimini kapsayan bir ara sınıfla başka bir dolaylama katmanını yeniden düzenlemeyi veya tanıtmayı deneyin. Gerçek kodu görmeden daha spesifik olmak zordur.


0

Yerel bir değişken yerine, bir kamu mülkiyeti beyan edilebilir; bu da atanmamış bir değişkenin başka bir potansiyel hatasını önlemelidir. genel dize S {get; Ayarlamak; }


-1

Atama işlemi başarısız olursa catch deyiminizin atanmamış değişkene geri null referansı olacaktır.


2
Atanmadı. Bu null bile değil (örnek ve statik değişkenlerin aksine).
Tom Hawtin - taktik

-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;
})();

O NE LAN? Neden aşağı oy? Kapsülleme, OOP'nin ayrılmaz bir parçasıdır. Çok hoş görünüyor.
çekirdek

2
Ben aşağı oy değildi, ama yanlış olan şey başlatılmamış bir dize döndürmektir.
Ben Voigt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.