web-dev-qa-db-ja.com

「catch」または「finally」のスコープの「try」で変数が宣言されないのはなぜですか?

C#およびJava(および他の言語も可能)では、「try」ブロックで宣言された変数は、対応する「catch」または「finally」ブロックのスコープ内にありません。たとえば、次のコードはコンパイルされません。

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

このコードでは、sがtryブロックのスコープ内にのみあるため、catchブロックのsへの参照でコンパイル時エラーが発生します。 (Javaでは、コンパイルエラーは「s cannot be resolve」です。C#では、「名前 's'は現在のコンテキストに存在しません」です。)

この問題の一般的な解決策は、tryブロック内ではなく、tryブロックの直前で変数を宣言することです。

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

ただし、少なくとも私にとっては、(1)これは不格好な解決策のように感じられ、(2)プログラマーが意図したよりも大きなスコープを持つ変数をもたらします(メソッドのコンテキストではなく、メソッドの残りの部分全体) try-catch-finally)。

私の質問は、この言語設計の決定の背後にある理論的根拠は何でしたか(Java、C#、および/または他の適用可能な言語で)?

128
Jon Schneider

2つのこと:

  1. 通常、Javaのスコープは2つのレベルのみです:グローバルと関数。ただし、try/catchは例外です(しゃれは意図していません)。例外がスローされ、例外オブジェクトに変数が割り当てられた場合それに対して、そのオブジェクト変数は「catch」セクション内でのみ使用可能であり、catchが完了するとすぐに破棄されます。

  2. (そして更に重要なことに)。 tryブロックのどこで例外がスローされたかを知ることはできません。変数が宣言される前の可能性があります。したがって、catch/finally句で使用できる変数を指定することはできません。スコーピングが提案されたとおりである次のケースを検討してください。

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

これは明らかに問題です。例外ハンドラに到達すると、sは宣言されません。キャッチは例外的な状況とfinallys must executeを処理することを意図しているので、安全であり、コンパイル時に問題を宣言することは実行時よりもはるかに優れています。

161

Catchブロックの宣言部分に到達したことをどのように確認できますか?インスタンス化が例外をスローした場合はどうなりますか?

55
Burkhard

従来、Cスタイル言語では、中括弧内で発生することは中括弧内にとどまります。変数の有効期間をそのようなスコープに広げることは、ほとんどのプログラマーにとって直感的ではないと思います。 try/catch/finallyブロックを別のレベルの中括弧で囲むことにより、目的を達成できます。例えば.

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

編集:私はすべてのルールを推測しますdoesには例外があります。有効なC++は次のとおりです。

int f() { return 0; }

void main() 
{
    int y = 0;

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

Xのスコープは、条件節、then節、else節です。

17
Ferruccio

他の誰もが基礎を育てました-ブロックで起こることはブロックにとどまります。しかし、.NETの場合、コンパイラが何をしていると考えているかを調べると役立つ場合があります。たとえば、次のtry/catchコードを使用します(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仕様にも固く設定されています。

スコープはあなたを保護しますが、時々それを回避する必要があります。時間が経つにつれて、あなたはそれに慣れ、自然に感じ始めます。他のみんなが言ったように、ブロックで起こることはそのブロックにとどまります。何かを共有したいですか?あなたはブロックの外に行かなければなりません...

9
John Rudy

とにかくC++では、自動変数のスコープは、それを囲む中括弧によって制限されます。なぜ中括弧の外側にtryキーワードを落とし込んで、これが異なると予想するのでしょうか?

8
ravenspoint

Ravenspointが指摘したように、誰もが変数が定義されているブロックに対してローカルであることを期待します。tryはブロックを導入し、catchも同様です。

trycatchの両方にローカルな変数が必要な場合は、両方をブロックで囲んでみてください。

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

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

簡単な答えは、Cおよびその構文を継承したほとんどの言語はブロックスコープであるということです。つまり、変数が1つのブロック、つまり{}内で定義されている場合、それがスコープです。

ちなみに、例外はJavaScriptです。JavaScriptの構文は似ていますが、関数スコープです。 JavaScriptでは、tryブロックで宣言された変数は、catchブロックのスコープ内にあり、それを含む関数内の他のすべての場所にあります。

5
dgvid

@burkhardには、なぜ適切に回答したのかという質問がありますが、追加したいメモとして、推奨されるソリューションの例は99.9999 +%の時間で良いのですが、良い習慣ではなく、使用する前にnullをチェックする方がはるかに安全ですtryブロック内でインスタンス化するか、tryブロックの前に変数を宣言するだけでなく、変数を何かに初期化します。例えば:

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

これにより、回避策でスケーラビリティが得られるため、tryブロックで実行していることが文字列の割り当てよりも複雑な場合でも、catchブロックからデータに安全にアクセスできるはずです。

4
Timothy Carter

MCTS Self-Paced Training Kit(Exam 70-536):Microsoft®.NET Framework 2.0—Application Development Foundationのレッスン2の「例外をスローおよびキャッチする方法」というタイトルのセクションによると、その理由tryブロック内の変数宣言の前に例外が発生した可能性があるということです(他の人が既に述べたように)。

25ページからの引用:

「前の例でStreamReader宣言がTryブロックの外側に移動されたことに注意してください。FinalブロックはTryブロック内で宣言された変数にアクセスできないため、これが必要です。例外が発生した場所によっては、Tryブロック内の変数宣言がまだ実行されていない可能性があるためです

4
hurst

誰もが指摘しているように、答えはほとんど「ブロックの定義方法」です。

コードをきれいにするいくつかの提案があります。 [〜#〜] arm [〜#〜] を参照してください

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

Closures もこれに対処することになっています。

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

UPDATE:ARMは、Java 7で実装されています。 http ://download.Java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

4
ykaganovich

あなたのソリューションはまさにあなたがすべきことです。 tryブロックで宣言に到達したかどうかはわかりませんが、これにより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
EndangeredMassa

変数はブロックレベルであり、そのTryまたはCatchブロックに制限されます。 ifステートメントで変数を定義することに似ています。この状況を考えてください。

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

文字列は宣言されないため、依存することはできません。

2
jW.

Tryブロックとcatchブロックは2つの異なるブロックだからです。

次のコードでは、ブロックAで定義されたがブロックBで表示されると期待しますか?

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

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

他のユーザーから指摘されているように、中括弧は、私が知っているほとんどすべてのCスタイル言語でスコープを定義します。

それが単純な変数である場合、なぜそれがスコープ内にあるかを気にしますか?大したことではありません。

c#では、複雑な変数の場合、IDisposableを実装する必要があります。その後、try/catch/finallyを使用して、finallyブロックでobj.Dispose()を呼び出すことができます。または、usingキーワードを使用して、コードセクションの最後にDisposeを自動的に呼び出します。

1
Charles Graham

変数の宣言の上にあるコードで例外がスローされた場合はどうなりますか。つまり、この場合、宣言自体は行われませんでした。

try {

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

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

指定した特定の例では、を初期化しても例外をスローできません。そのため、おそらくその範囲を拡張できると思います。

ただし、一般に、初期化式は例外をスローする可能性があります。初期化子が例外をスローした(または、それが発生した別の変数の後に宣言された)変数がcatch/finallyのスコープ内にあることは意味がありません。

また、コードが読みやすくなります。 C(およびそれに続く言語(C++、JavaおよびC#)を含む)のルールは単純です。変数スコープはブロックに従います。

変数をtry/catch/finallyのスコープ内に配置し、他のどこにも配置しない場合は、全体を別のブレースセット(裸のブロック)でラップし、tryの前に変数を宣言します。

1
Steve Jessop

それらが同じスコープ内にない理由の一部は、tryブロックの任意の時点で例外をスローした可能性があるためです。それらが同じスコープ内にある場合、待機中の災害です。例外がスローされた場所によっては、さらにあいまいになる可能性があるためです。

少なくともtryブロックの外側で宣言されている場合、例外がスローされたときに少なくとも変数が何であるかは確実にわかります。 tryブロックの前の変数の値。

1
zxcv

In Pythonは、それらを宣言する行がスローしなかった場合、catch/finallyブロックに表示されます。

1
adal

あなたの例ではうまくいかないのは奇妙ですが、これと同じようなものを見てください:

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

これにより、コード1が破損した場合、キャッチはnull参照例外をスローします。 try/catchのセマンティクスはかなりよく理解されていますが、sは初期値で定義されているため、これは面倒なコーナーケースになります。

繰り返しますが、理論的には、これは分離された定義(String s; s = "1|2";)、またはその他の条件セットがありますが、通常は単に「いいえ」と言う方が簡単です。

さらに、スコープのセマンティクスを例外なくグローバルに定義できます。具体的には、{}それらはすべての場合に定義されています。マイナーポイントですが、ポイントです。

最後に、必要なことを行うために、try catchを囲む括弧のセットを追加できます。必要な範囲を提供しますが、読みやすさは少し犠牲になりますが、それほど多くはありません。

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

ローカル変数を宣言すると、スタックに配置されます(一部の型ではオブジェクトの値全体がスタックにあり、他の型では参照のみがスタックにあります)。 tryブロック内に例外がある場合、ブロック内のローカル変数は解放されます。これは、スタックがtryブロックの開始時の状態に「巻き戻される」ことを意味します。これは仕様です。 try/catchがブロック内のすべての関数呼び出しからバックアウトし、システムを機能状態に戻す方法です。このメカニズムがないと、例外が発生したときに何かの状態を確認することはできません。

エラー処理コードを、tryブロック内で値が変更された外部で宣言された変数に依存させることは、私にとって悪い設計のように思えます。あなたがやっているのは、基本的に情報を得るために意図的にリソースをリークすることです(この特定のケースでは、情報をリークしているだけなのでそれほど悪くはありませんが、それが他のリソースであると想像してください?未来)。エラー処理をより細かく行う必要がある場合は、tryブロックを小さなチャンクに分割することをお勧めします。

1
Wedge

Try catchを使用する場合、ほとんどの場合、スローされる可能性のあるエラーを知っている必要があります。これらの例外クラスは、通常、例外について必要なすべてを伝えます。そうでない場合は、独自の例外クラスを作成し、その情報を渡す必要があります。このように、例外は自己説明的であるため、tryブロック内から変数を取得する必要はありません。したがって、これをたくさん行う必要がある場合は、デザインについて考え、他の方法があるかどうかを考えてみてください。例外が発生することを予測するか、例外から発生する情報を使用して、自分自身を再スローすることができます詳細情報付きの例外。

1

私の考えは、tryブロック内の何かが例外をトリガーしたため、その名前空間の内容は信頼できません。つまり、catchブロック内の文字列「s」を参照すると、さらに別の例外がスローされる可能性があります。

0
jpbarto

コンパイルエラーがスローされず、メソッドの残りの部分で宣言できる場合、tryスコープ内でのみ宣言する方法はありません。変数が存在するはずの場所を明示することを強制し、仮定を行いません。

0
kemiller2002

スコーピングブロックの問題をしばらく無視した場合、明確に定義されていない状況では、コンパイラーはより一生懸命作業する必要があります。これは不可能ではありませんが、スコーピングエラーにより、コードの作成者は、コードの意味を理解するように強制されます(catchブロックで文字列sがnullになる可能性がある)。コードが正当な場合、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ブロックでは、これを保証できません。

そのため、コンパイラーは多くの作業を行う必要がありますが、実際にはあまり利点がなく、おそらく人々を混乱させて、なぜtry/catchが異なるのかを尋ねさせます。

一貫性に加えて、何も凝ったものを許可せず、言語全体で使用されている既に確立されたスコープセマンティクスに従うことにより、コンパイラとCLRは、catchブロック内の変数の状態をより強力に保証できます。存在し、初期化されていること。

言語設計者は、問題と範囲が明確に定義されているsinglockのような他のコンストラクトで良い仕事をしたことに注意してください。

例えばsingキーワードとIDisposableのオブジェクト:

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

以下と同等です:

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

Try/catch/finallyを理解するのが難しい場合は、リファクタリングを行うか、達成しようとしているもののセマンティクスをカプセル化する中間クラスを使用して、別の間接層を導入してみてください。実際のコードを見ることなく、より具体的にすることは困難です。

0
Robert Paulson

ローカル変数の代わりに、パブリックプロパティを宣言できます。これにより、未割り当て変数の別の潜在的なエラーも回避できます。パブリック文字列S {get;セットする; }

0
usefulBee

C#Spec (15.2)は、「ブロックで宣言されたローカル変数または定数のスコープはブロックです」と述べています。

(最初の例では、tryブロックは「s」が宣言されているブロックです)

0
tamberg