157

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

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

このコードでは、catch ブロック内の s への参照でコンパイル時エラーが発生します。これは、s が try ブロック内でのみスコープ内にあるためです。(Java では、コンパイル エラーは「s を解決できません」です。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#、および/または他の適用可能な言語) の背後にある理論的根拠は何ですか?

4

28 に答える 28

186

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 は宣言されていません。catch は例外的な状況を処理するためのものであり、finalsを実行する必要があることを考えると、安全であり、コンパイル時にこれを問題として宣言することは、実行時よりもはるかに優れています。

于 2008-09-18T18:01:57.237 に答える
55

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

于 2008-09-18T17:58:40.020 に答える
22

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

... 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 のスコープは、条件、then 節、else 節です。

于 2008-09-18T18:03:22.177 に答える
11

他の誰もが基本的なことを持ち出しました - ブロックで起こることはブロックにとどまります。しかし、.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 仕様でも厳密に設定されています。

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

于 2008-09-18T21:48:08.067 に答える
9

いずれにせよ、C++ では、自動変数のスコープはそれを囲む中括弧によって制限されます。中括弧の外側に try キーワードを挿入することで、これが異なると考える人がいるでしょうか?

于 2008-09-18T17:59:19.350 に答える
7

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

と の両方にローカルな変数が必要な場合は、両方trycatchブロックで囲みます。

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

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}
于 2008-09-18T18:03:58.853 に答える
5

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

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

于 2008-09-18T18:06:59.580 に答える
5

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

25ページからの引用:

前の例では、StreamReader 宣言が Try ブロックの外に移動されていることに注意してください。これが必要なのは、Finally ブロックが Try ブロック内で宣言された変数にアクセスできないためです。トライブロックがまだ実行されていない可能性があります。」

于 2008-09-19T07:31:29.193 に答える
4

@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ブロックからデータに安全にアクセスできるはずです。

于 2008-09-18T18:12:04.187 に答える
4

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

コードをよりきれいにするための提案がいくつかあります。アームを見る

 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

于 2008-09-18T18:37:26.747 に答える
2

あなたの例ではそれが機能しないのは奇妙ですが、これに似たものを見てください:

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

これにより、コード1が壊れた場合、キャッチはnull参照例外をスローします。try / catchのセマンティクスはかなりよく理解されていますが、sは初期値で定義されているため、これは厄介なコーナーケースになります。したがって、理論的にはnullになることはありませんが、共有セマンティクスではnullになります。

String s; s = "1|2";繰り返しますが、これは理論的には、分離された定義( )またはその他の条件のセットのみを許可することで修正できますが、一般的には「いいえ」と言う方が簡単です。

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

最後に、やりたいことを行うために、trycatchの周りに角かっこを追加できます。少し読みやすくなりますが、あまり多くはありませんが、必要な範囲を提供します。

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}
于 2008-09-18T18:11:52.237 に答える
2

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

于 2008-09-19T06:00:20.113 に答える
2

あなたの解決策はまさにあなたがすべきことです。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
于 2008-09-18T18:00:22.840 に答える
2

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

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

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

于 2008-09-18T18:02:04.740 に答える
2

try ブロックと catch ブロックは 2 つの異なるブロックであるためです。

次のコードでは、ブロック A で定義された s がブロック B で可視になると思いますか?

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

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}
于 2008-09-18T18:05:57.180 に答える
1

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

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

于 2008-09-18T18:12:41.627 に答える
1

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

try {

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

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}
于 2010-12-29T10:23:14.093 に答える
1

C# 仕様(15.2) には、「ブロックで宣言されたローカル変数または定数のスコープはブロックである」と記載されています。

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

于 2008-09-18T18:14:59.060 に答える
1

try catch がある場合は、ほとんどの場合、それがスローする可能性のあるエラーを知っている必要があります。これらの例外クラスは通常、例外について必要なすべてを伝えます。そうでない場合は、独自の例外クラスを作成し、その情報を渡す必要があります。そうすれば、例外は自明であるため、try ブロック内から変数を取得する必要はありません。したがって、これを何度も行う必要がある場合は、自分が設計していることを考えて、例外が来ることを予測するか、例外から来る情報を使用して、独自の方法を再スローできる方法があるかどうかを考えてみてください。より多くの情報を持つ例外。

于 2008-09-18T18:19:20.517 に答える
1

あなたが与えた特定の例では、初期化 s は例外をスローできません。そのため、その範囲を拡張できる可能性があると考えるでしょう。

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

また、コードの可読性も低下します。C (および C++、Java、C# など、C に続く言語) のルールは単純です。変数のスコープはブロックに従います。

変数を try/catch/finally のスコープ内に置き、それ以外の場所には入れないようにする場合は、全体を別の中かっこのセット (裸のブロック) でラップし、try の前に変数を宣言します。

于 2008-09-18T18:02:53.520 に答える
1

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

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

于 2008-09-18T18:04:06.217 に答える
1

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

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

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

于 2008-09-18T18:22:55.500 に答える
0

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

于 2013-07-23T17:40:09.427 に答える
0

スコープ ブロックの問題をしばらく無視すると、コンパイラは、明確に定義されていない状況でより多くの作業を行う必要があります。これは不可能ではありませんが、スコープ エラーにより、コードの作成者であるあなたは、自分が記述し​​たコードの意味を認識しなければならなくなります (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 ブロック内の変数の状態をより確実に保証できます。存在し、初期化されていること。

言語設計者は、問題と範囲が明確に定義されているusinglockなどの他の構成要素をうまく使用して、より明確なコードを記述できるようになっていることに注意してください。

たとえば、次のIDisposableオブジェクトを使用したusingキーワード:

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 を理解するのが難しい場合は、リファクタリングするか、達成しようとしているもののセマンティクスをカプセル化する中間クラスを使用して別の間接レイヤーを導入してみてください。実際のコードを見なければ、より具体的に説明することは困難です。

于 2008-09-18T23:56:43.373 に答える
0

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

于 2008-09-18T17:58:42.657 に答える
0

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

于 2008-09-18T17:58:58.490 に答える
-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;
})();
于 2008-09-18T23:58:27.987 に答える
-1

割り当て操作が失敗した場合、catch ステートメントは割り当てられていない変数への null 参照を返します。

于 2008-09-18T18:01:18.907 に答える